简单整合
使用springboot整合shiro 实现登录、权限控制。 1、用户类、角色类、权限类
@Data
@AllArgsConstructor
public class User {
private String token;
private String username;
private String password;
private Set<Role> roles;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public User() {
}
}
@Data
@AllArgsConstructor
public class Role {
private String id;
private String roleName;
private Set<Permissions> permissions;
}
@Data
@AllArgsConstructor
public class Permissions {
private String id;
private String permissionsName;
}
2、maven文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro-spring.version}</version>
</dependency>
3、realm文件,配置登录验证、权限逻辑
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
User user = (User) primaryPrincipal;
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
simpleAuthorizationInfo.addRole(role.getRoleName());
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
}
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
System.out.println(authenticationToken.toString());
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User byUsername = userService.getByUsername(username);
if (byUsername == null) {
return null;
}
return new SimpleAuthenticationInfo(byUsername, byUsername.getPassword(), "");
}
}
4、编写shiro配置类
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
map.put("/logout", "logout");
map.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
5、编写userService模拟数据库查询
@Service
public class UserService {
private volatile static Map<String, User> users = new ConcurrentHashMap<>();
static {
Permissions permissions1 = new Permissions("1", "select");
Permissions permissions2 = new Permissions("2", "add");
Set<Permissions> permissionsSet = new HashSet<>();
permissionsSet.add(permissions1);
permissionsSet.add(permissions2);
Role role = new Role("1", "admin", permissionsSet);
Set<Role> roleSet = new HashSet<>();
roleSet.add(role);
User user = new User("1", "kaico", "123456", roleSet);
users.put(user.getUsername(), user);
Set<Permissions> permissionsSet1 = new HashSet<>();
permissionsSet1.add(permissions1);
Role role1 = new Role("2", "user", permissionsSet1);
Set<Role> roleSet1 = new HashSet<>();
roleSet1.add(role1);
User user1 = new User("2", "jing", "123456", roleSet1);
users.put(user1.getUsername(), user1);
}
public User getByUsername(String username){
return users.get(username);
}
}
6、编写全局异常捕获类,用于处理没有权限抛的异常
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("没有通过权限验证!", e);
return "没有通过权限验证!";
}
}
7、编写测试类
@RestController
@Log4j2
public class TestController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(String username, String password){
System.out.println("login");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "没有权限";
}
return "login success";
}
@RequestMapping(value = "/add", method = RequestMethod.GET)
@RequiresPermissions(value = "add")
public String add(){
System.out.println("add");
return "add";
}
@RequestMapping(value = "/select", method = RequestMethod.GET)
@RequiresPermissions(value = "select")
public String select(){
System.out.println("select");
return "select";
}
@RequestMapping(value = "/loginOut", method = RequestMethod.GET)
public String loginOut(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "loginOut";
}
@RequestMapping(value = "/error", method = RequestMethod.GET)
public String error(){
return "error";
}
}
shiro密码加密管理
数据库中保存的密码都是明文的,一旦数据库数据泄露,那就会造成不可估算的损失,所以我们通常都会使用非对称加密,简单理解也就是不可逆的加密,而 md5 加密算法就是符合这样的一种算法。
既然相同的密码 md5 一样,那么我们就让我们的原始密码再加一个随机数,然后再进行 md5 加密,这个随机数就是我们说的盐(salt),这样处理下来就能得到不同的 Md5 值,当然我们需要把这个随机数盐也保存进数据库中,以便我们进行验证。
1、修改realm的验证配置:主要修改返回的 SimpleAuthenticationInfo 对象增加盐参数。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
System.out.println(authenticationToken.toString());
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User byUsername = userService.getByUsername(username);
if (byUsername == null) {
return null;
}
return new SimpleAuthenticationInfo(byUsername, byUsername.getPassword(), ByteSource.Util.bytes(username), "");
}
2、修改shiro配置文件:给userRealm bean 设置加密算法
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
credentialsMatcher.setHashIterations(1);
userRealm.setCredentialsMatcher(credentialsMatcher);
return userRealm;
}
3、模拟的数据库数据
session管理
shiro中的session特性
- 基于POJO/J2SE:shiro中session相关的类都是基于接口实现的简单的java对象(POJO),兼容所有java对象的配置方式,扩展也更方便,完全可以定制自己的会话管理功能 。
- 简单灵活的会话存储/持久化:因为shiro中的session对象是基于简单的java对象的,所以你可以将session存储在任何地方,例如,文件,各种数据库,内存中等。
- 容器无关的集群功能:shiro中的session可以很容易的集成第三方的缓存产品完成集群的功能。例如,Ehcache + Terracotta, Coherence, GigaSpaces等。你可以很容易的实现会话集群而无需关注底层的容器实现。
- 异构客户端的访问:可以实现web中的session和非web项目中的session共享。
- 会话事件监听:提供对对session整个生命周期的监听。
- 保存主机地址:在会话开始session会存用户的ip地址和主机名,以此可以判断用户的位置。
- 会话失效/过期的支持:用户长时间处于不活跃状态可以使会话过期,调用touch()方法,可以主动更新最后访问时间,让会话处于活跃状态。
- 透明的Web支持:shiro全面支持Servlet 2.5中的session规范。这意味着你可以将你现有的web程序改为shiro会话,而无需修改代码。
- 单点登录的支持:shiro session基于普通java对象,使得它更容易存储和共享,可以实现跨应用程序共享。可以根据共享的会话,来保证认证状态到另一个程序。从而实现单点登录。
常用api
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
与web中的 HttpServletRequest.getSession(boolean create) 类似! Subject.getSession(true)。即如果当前没有创建session对象会创建一个; Subject.getSession(false),如果当前没有创建session对象则返回null。
实现session管理、共享
注意:session存入redis中,用户类User需要序列化。
1、增加redis依赖,增加redis配置信息,shiro配置信息
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
spring:
redis:
host: www.kaicostudy.com
port: 6379
database: 0
shiro:
jessionid: kaico
loginUrl: /login
session:
expireTime: 43200000
2、增加session dao层操作,实线将session存储到redis中
@Service
@Log4j2
public class RedisSessionDao extends AbstractSessionDAO {
@Value("${shiro.session.expireTime}")
private Long expireTime;
@Autowired
private RedisTemplate redisTemplate;
public RedisSessionDao() {
super();
}
public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
super();
this.expireTime = expireTime;
this.redisTemplate = redisTemplate;
}
@Override
public void update(Session session) throws UnknownSessionException {
log.info("===============update================");
if (session == null || session.getId() == null) {
return;
}
session.setTimeout(expireTime);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
}
@Override
public void delete(Session session) {
log.info("===============delete================");
if (null == session) {
return;
}
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
@Override
public Collection<Session> getActiveSessions() {
log.info("==============getActiveSessions=================");
return redisTemplate.keys("*");
}
@Override
protected Serializable doCreate(Session session) {
log.info("===============doCreate================");
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
log.info("==============doReadSession=================");
if (sessionId == null) {
return null;
}
return (Session) redisTemplate.opsForValue().get(sessionId);
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
3、将重写的RedisSessionDao 接入到shiro 中的sessionManager,shiro配置类代码
@Log4j2
@Configuration
public class ShiroConfig {
@Value("${shiro.loginUrl}")
private String loginUrl;
@Value("${shiro.jessionid}")
private String jessionId;
@Value("${shiro.session.expireTime}")
private Long expireTime;
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
map.put("/logout", "logout");
map.put("/**", "authc");
log.info("loginUrl:" + loginUrl);
shiroFilterFactoryBean.setLoginUrl(loginUrl);
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
securityManager.setSessionManager(defaultWebSessionManager());
return securityManager;
}
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
credentialsMatcher.setHashIterations(1);
userRealm.setCredentialsMatcher(credentialsMatcher);
return userRealm;
}
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name="sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie(jessionId);
return simpleCookie;
}
@Bean
public RedisSessionDao getRedisSessionDao(){
return new RedisSessionDao();
}
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(expireTime);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionDAO(getRedisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}
}
实现shiro缓存
实现缓存的作用,这样每次请求接口不用走realm权限认证的方法了。提高效率。
工具类
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public static ApplicationContext getContext(){
return context;
}
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}
public class ByteSourceUtils {
public static ByteSource bytes(byte[] bytes) {
return new SimpleByteSource(bytes);
}
public static ByteSource bytes(String arg0) {
return new SimpleByteSource(arg0.getBytes());
}
}
redis缓存类,实现shiro的 Cache 接口,使用redis实现缓存
@Log4j2
public class RedisCache<K,V> implements Cache<K,V> {
private String name ;
public RedisCache(){
}
public RedisCache(String name){
log.info("name="+name);
this.name = name;
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Override
public V get(K k) throws CacheException {
log.info("------------------get from "+k.toString());
return (V) getRedisTemplate().opsForHash().get(name,k.toString());
}
@Override
public V put(K k, V v) throws CacheException {
log.info("------------------put "+ v +" with "+k.toString());
getRedisTemplate().opsForHash().put(name,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {
log.info("------------------delete "+k.toString());
getRedisTemplate().opsForHash().delete(name,k.toString());
return null;
}
@Override
public void clear() throws CacheException {
log.info("------------------clear");
getRedisTemplate().opsForHash().delete(name);
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
return getRedisTemplate().opsForHash().keys(this.name);
}
@Override
public Collection<V> values() {
return getRedisTemplate().opsForHash().values(this.name);
}
}
RedisCacheManager 管理器
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new RedisCache<K,V>(s);
}
}
由于shiro中提供的simpleByteSource实现,没有实现序列化,所以在认证时出现错误信息
public class SimpleByteSource extends org.apache.shiro.util.SimpleByteSource implements Serializable {
private static final long serialVersionUID = 5528101080905698238L;
public SimpleByteSource(byte[] bytes) {
super(bytes);
}
}
realm认证时,传入的盐用 MyByteSource 包装
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
System.out.println(authenticationToken.toString());
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User byUsername = userService.getByUsername(username);
if (byUsername == null) {
return null;
}
return new SimpleAuthenticationInfo(byUsername, byUsername.getPassword(), ByteSourceUtils.bytes(username) , getName());
}
修改shiro配置类 的ream bean配置
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
credentialsMatcher.setHashIterations(1);
userRealm.setCredentialsMatcher(credentialsMatcher);
userRealm.setCachingEnabled(true);
userRealm.setAuthenticationCachingEnabled(true);
userRealm.setAuthorizationCachingEnabled(true);
userRealm.setCacheManager(new RedisCacheManager());
return userRealm;
}
|