Shiro之与SpringBoot整合

一、Shiro与SpringBoot整合

1.添加环境依赖

	<dependency>
	  <groupId>org.apache.shiro</groupId>
	  <artifactId>shiro-spring</artifactId>
	  <version>1.3.2</version>
	</dependency>
	<dependency>
	  <groupId>org.apache.shiro</groupId>
	  <artifactId>shiro-core</artifactId>
	  <version>1.3.2</version>
	</dependency>

2.自定义realm

自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法。

authorization为授权,批准的意思,即获取用户的角色和权限等信息;

authentication认证,身份验证的意思,即登录时验证用户的合法性,比如验证用户名和密码。
	public class CustomRealm extends AuthorizingRealm {
	  @Override
	  public void setName(String name) {
	    super.setName("customRealm");
	 }
	  @Autowired
	  private UserService userService;
	  /**
	  * 授权
	  */
	  @Override
	  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		//获取认证的用户数据
		User user = (User) SecurityUtils.getSubject().getPrincipal();
		String userName = user.getUserName();

		//构造认证数据
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

		// 获取用户角色集
		List<Role> roleList = userService.findByUserName(userName);
		Set<String> roleSet = new HashSet<String>();
		for (Role role : roleList) {
			roleSet.add(role.getName());
		}

//		for (Role role : roleList) {
//			simpleAuthorizationInfo.addRole(role.getName());
//		}
		simpleAuthorizationInfo.setRoles(roleSet);

		// 获取用户权限集
		List<Permission> permissionList = userPermissionMapper.findByUserName(userName);
		Set<String> permissionSet = new HashSet<String>();
		for (Permission p : permissionList) {
			permissionSet.add(p.getName());
		}

//		for (Permission permission:permissionList) {
//			simpleAuthorizationInfo.addStringPermission(permission.getName());
//		}
		simpleAuthorizationInfo.setStringPermissions(permissionSet);

		return simpleAuthorizationInfo;
	 }


	  /**
	  * 认证
	  */
	 @Override
	 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取登录的upToken
        UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
        //2.获取输入的用户名密码
        String username = oken.getUsername();
        String password = new String(upToken.getPassword());
        //3.数据库查询用户
        User user = userService.findByName(username);
        //4.用户存在并且密码匹配存储用户数据
        // Shiro具有丰富的运行时AuthenticationException层次结构,可以准确指出认证失败的原因
        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误!");
        }
        if (!password.equals(user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        if (user.getStatus().equals("0")) {
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
        return simpleAuthenticationInfo;
    }
	}

3.编写登录方法

	@RequestMapping(value="/login")
	  public String login(String username,String password) {
		   Subject subject = SecurityUtils.getSubject();
			UsernamePasswordToken token = new UsernamePasswordToken(username,password);
			try {
				subject.login(token);
				return "登录成功!";
			} catch (UnknownAccountException e) {
				return e.getMessage();
			} catch (IncorrectCredentialsException e) {
				return e.getMessage();
			} catch (LockedAccountException e) {
				return e.getMessage();
			} catch (AuthenticationException e) {
				return "认证失败!";
			}
	 }

4.Shiro配置

	@Configuration
	public class ShiroConfiguration {
	  //配置自定义的Realm
	  @Bean
	  public CustomRealm getRealm() {
	    return new CustomRealm();
	 }
	//配置安全管理器
	  @Bean
	  public SecurityManager securityManager(CustomRealm realm) {
	    //将自定义的realm交给安全管理器统一调度管理,使用默认的安全管理器
	   //1.构造方式
	   // DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager(realm);
		
		//2.属性设值方式
	    DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager();
	    securityManager.setRealm(realm);
	    return securityManager;
	 }
	 
	  //配置Shiro顾虑器Filter工厂,设置对应的过滤条件和跳转条件
	  @Bean
	  public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
	    //1.创建shiro过滤器工厂
	    ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
	    //2.设置安全管理器
	    filterFactory.setSecurityManager(securityManager);
	    //3.配置登录页面,登录成功页面,验证未成功页面
	    filterFactory.setLoginUrl("/index"); //设置登录页面
	    // 设置登录成功后跳转的url
        shiroFilterFactoryBean.setSuccessUrl("/home");
         //授权失败跳转页面
	    filterFactory.setUnauthorizedUrl("/index");
	    // 配置退出过滤器,具体退出代码Shiro已经实现
        filterChainDefinitionMap.put("/logout", "logout");
	    //4.添加shiro内置过滤器,实现权限相关的url拦截
        /**
         * 常见过滤器:
         * anon:无需认证(登录)可以访问
         * authc:必须认证才可以访问
         * user:如果使用Remember Me的功能,可以直接访问
         * perms:该资源必须得到资源权限才可以访问
         * role:该资源必须得到角色权限才可以访问
         * filterChain基于短路机制,即最先匹配原则 /user/**=anon  /user/addUser=authc 永远不会执行
		*/
	    Map<String,String> filterMap = new LinkedHashMap<String,String>();
	     //静态资源不拦截
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
	    // anno  :匿名访问,表明此链接所有人可以访问
	    filterMap.put("/index", "anon");
	    //authc  :认证后访问(表明此链接需登录认证成功之后可以访问)
	    //参数可写多个,表示是拥有某个或某些角色才能通过,不具备则跳转到setUnauthorizedUrl设置地址
	    filterMap.put("/home/details", "roles[admin]");
	    //参数可写多个,表示需要拥有某个或某些权限才能通过,不具备则跳转到setUnauthorizedUrl设置地址
	    filterMap.put("/home/details", "perms[getDetails]");
	    //authc  :认证后访问,表明此链接需登录认证成功之后可以访问
	    filterMap.put("/home/**", "authc");
	    //5.设置过滤器
	    filterFactory.setFilterChainDefinitionMap(filterMap);
	    return filterFactory;
	 }
	 
	  //配置shiro注解支持
	  @Bean
	  public AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
	    AuthorizationAttributeSourceAdvisor advisor = new
		AuthorizationAttributeSourceAdvisor();
	    advisor.setSecurityManager(securityManager);
	    return advisor;
	 }
	}

5.权限相关注解

使用权限注解需配置开启注解支持

// 表示当前Subject已经通过login进行了身份验证;即Subject.isAuthenticated()返回true。
@RequiresAuthentication

// 表示当前Subject已经身份验证或者通过记住我登录的。
@RequiresUser

// 表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresGuest

// 表示当前Subject需要角色admin和user。  
@RequiresRoles(value = {"admin", "user"}, logical = Logical.AND)

// 表示当前Subject需要权限user:add或user:update。
@RequiresPermissions(value = {"user:add", "user:update"}, logical = Logical.OR)

6.Shiro授权

1.基于配置授权

 		//4.配置过滤器集合
	    Map<String,String> filterMap = new LinkedHashMap<String,String>();
	    // anno  :匿名访问,表明此链接所有人可以访问
	    filterMap.put("/index", "anon");
	    //authc  :认证后访问(表明此链接需登录认证成功之后可以访问)
	    //参数可写多个,表示是拥有某个或某些角色才能通过,不具备则跳转到setUnauthorizedUrl设置地址
	    filterMap.put("/home/details", "roles[admin]");
	    //参数可写多个,表示需要拥有某个或某些权限才能通过,不具备则跳转到setUnauthorizedUrl设置地址
	    filterMap.put("/home/details", "perms[getDetails]");
	    //authc  :认证后访问,表明此链接需登录认证成功之后可以访问
	    filterMap.put("/**", "authc");
	    //5.设置过滤器
	    filterFactory.setFilterChainDefinitionMap(filterMap);

2.基于注解授权

基于角色授权:

  @RequiresRoles(value = "admin")
  public String select() {
    return "查询成功";
 }

基于权限授权:

  @RequiresPermissions(value = "getDetails")
  public String select() {
    return "查询成功";
 }

7.自定义权限异常处理器

1.基于配置方式进行授权,若用户不具备权限,目标地址不会执行。会跳转到filterFactory.setUnauthorizedUrl指定的url地址。

2.基于注解配置方式进行授权,若用户不具备操作权限,目标方法不会被执行,并且会抛出AuthorizationException 异常。

/**
 * 自定义权限异常处理器
 */
@ControllerAdvice
public class AuthorizationExceptionHandler {

    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseBody
    public String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException e) {
		return "您没有相关权限!";
    }
}

二、Shiro之编码加密

1.添加密码凭证匹配器

	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher(){
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		// 使用md5 算法进行加密
		hashedCredentialsMatcher.setHashAlgorithmName("md5");
		// 设置散列次数: 意为加密几次
		hashedCredentialsMatcher.setHashIterations(1024);
		return hashedCredentialsMatcher;
	}

2.注册给Realm

  @Bean
	  public CustomRealm getRealm() {
		  CustomRealm realm = new CustomRealm();
		  realm.setCredentialsMatcher(hashedCredentialsMatcher());
		  return realm;
	  }

3.注册时对密码加密

    /**
     * 密码加密
     * @param password 明文密码
     * @param salt 盐
     * @return
     */
    public static String md5Hash(String password, String salt) {
        return new Md5Hash(password, salt, 1024).toString();
    }

4.自定义Realm认证方法作以下处理

 return new SimpleAuthenticationInfo(用户,用户密码,盐,当前Realm的类名);
  return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());

5.登录调用UsernamePasswordToken

  UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
  Subject subject = SecurityUtils.getSubject();
  subject.login(usernamePasswordToken);

三、Shiro之记住我

当成功登录后,关闭浏览器然后再打开搜保护资源时,页面会跳转到登录页,之前的登录因为浏览器的关闭已经失效了,Shiro为提供了Remember Me的功能,用户的登录状态不会因为浏览器的关闭而失效,直到Cookie过期。

1.修改Shiro配置

基于上述配置文件添加cookie对象与cookie管理对象

	/**
	 * cookie对象
	 * @return
	 */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //参数remember是cookie的名称,对应前端的checkbox的name = remember
        SimpleCookie simpleCookie = new SimpleCookie("remember");
        // 设置cookie的过期时间,单位为秒
        simpleCookie.setMaxAge(60*60*60*24*7);
        return simpleCookie;
    }

	/**
	 * cookie管理对象
	 * @return
	 */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // rememberMe cookie加密的密钥 
        cookieRememberMeManager.setCipherKey(Base64.decode("abcdefg"));
        return cookieRememberMeManager;
    }

将cookie管理对象设置到SecurityManager中

	@Bean
	public SecurityManager securityManager(CustomRealm realm) {
		//将自定义的realm交给安全管理器统一调度管理,使用默认的安全管理器
		//1.构造方式
		// DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager(realm);

		//2.属性设值方式
		DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager();
		securityManager.setRealm(realm);
		
		//设置记住我
		securityManager.setRememberMeManager(rememberMeManager());
		return securityManager;
	}

修改权限配置

将ShiroFilterFactoryBean的filterMap.put("/**", "authc");更改为filterMap.put("/**", "user")

user指的是用户认证通过或者配置了Remember Me记住用户登录状态后可访问。

2.更改LoginController

	@RequestMapping(value="/login")
	public String login(String username,String password, Boolean rememberMe) {
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken(username,password,rememberMe);
		try {
			subject.login(token);
			return "登录成功!";
		} catch (UnknownAccountException e) {
			return e.getMessage();
		} catch (IncorrectCredentialsException e) {
			return e.getMessage();
		} catch (LockedAccountException e) {
			return e.getMessage();
		} catch (AuthenticationException e) {
			return "认证失败!";
		}
	}

四、Shiro之使用缓存

权限在短时间内基本不会变化,加入缓存可以使权限相关操作尽可能快,避免频繁访问操作数据库

Shiro提供了Cache的抽象,并没有直接提供相应的实现,需要去进行缓存实现,常用Redis和Ehcache缓存实现

1.Redis实现缓存

引入Redis依赖

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>x.x.x</version>
</dependency>

配置Redis

spring:
  redis:
    host: localhost
    port: 6379
    pool:
      max-active: 8
      max-wait: -1
      max-idle: 8
      min-idle: 0
    timeout: 0

修改Shiro配置

在Shiro配置文件添加RedisManager与RedisCacheManager配置

	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		return redisManager;
	}

	public RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

在SecurityManager中加入RedisCacheManager

	@Bean
	public SecurityManager securityManager(CustomRealm realm) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(realm);
		securityManager.setRememberMeManager(rememberMeManager());
		//设置记住我
		securityManager.setCacheManager(cacheManager());
		return securityManager;
	}

2.Ehcache实现缓存

添加Ehcache依赖

	<!-- shiro ehcache -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
           <version>x.x.x</version>
        </dependency>
        
        <!-- ehchache -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>x.x.x</version>
        </dependency>

Ehcache配置

在src/main/resource/config路径下新增shiro-ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
	updateCheck="false">
	<diskStore path="java.io.tmpdir/Tmp_EhCache" />
	<defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120" />
        
	<!-- 登录记录缓存锁定1小时 -->
    <cache name="passwordRetryCache"
        maxEntriesLocalHeap="2000"
        eternal="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        statistics="true" />
</ehcache>

Shiro配置
在Shiro配置中添加Ehcache缓存配置

@Bean
public EhCacheManager getEhCacheManager() {
    EhCacheManager em = new EhCacheManager();
    em.setCacheManagerConfigFile("classpath:config/shiro-ehcache.xml");
    return em;
}

将缓存对象注入到SecurityManager中

@Bean  
public SecurityManager securityManager(){  
    DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
    securityManager.setRealm(shiroRealm());
    securityManager.setRememberMeManager(rememberMeManager());
    securityManager.setCacheManager(getEhCacheManager());
    return securityManager;  
}

五、添加Shiro会话管理

在shiro里所有的用户的会话信息都会由Shiro来进行控制,shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。通过Shiro的会话管理器(SessionManager)进行统一的会话管理。

shiro提供了三个默认实现:

1. DefaultSessionManager:用于JavaSE环境

2. ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。(默认会话管理器)

3. DefaultWebSessionManager:用于web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)。

在web程序中,通过shiro的Subject.login()方法登录成功后,用户的认证信息实际上是保存在HttpSession中

如果使用默认会话管理,用户信息只会保存到一台服务器上,在分布式系统或者微服务架构下,都是通过统一的认证中心进行用户认证,此时就需要使用DefaultWebSessionManager,由自己维护会话。

1.添加依赖

	<dependency>
	<groupId>org.crazycake</groupId>
	<artifactId>shiro-redis</artifactId>
	<version>3.2.3</version>
	</dependency>

2.配置redis

 redis:
   host: 127.0.0.1
   port: 6379

 
#自定义redis key
redis:
  host: 127.0.0.1
  password:
  port: 6379
  dataBase: 6
 

3.使用默认会话管理器

使用DefaultWeb Session Manager自己维护会话,但是需要配置Session dao的实现

	// 缓存配置
	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		return redisManager;
	}

	public RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

	//配置SessionDao,使用shiro-redis基于redis的sessionDao的实现
	@Bean
	public RedisSessionDAO sessionDAO() {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		return redisSessionDAO;
	}

	//会话管理器
	@Bean
	public SessionManager sessionManager() {
	    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
	    sessionManager.setSessionDAO(sessionDAO());
	    return sessionManager;
	}

注册session管理器与自定义缓存管理器

@Bean  
public SecurityManager securityManager(){  
    DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager();
	      // 自定义session管理注册到安全管理器中
    	securityManager.setSessionManager(sessionManager());
    	// 自定义缓存实现注册到安全管理器中
   		securityManager.setCacheManager(cacheManager());
	    return securityManager;
}

4.自定义shiro会话管理器

自定义Session dao维护会话操作,在 Session Dao指定Reds配置

/**
* 自定义的sessionManager
*/
public class CustomSessionManager extends DefaultWebSessionManager {
    /**
     * 头信息中有sessionid
     * 请求头:Authorization: sessionid
     * sessionid从请求头中获取
     * <p>
     * 指定sessionId的获取方式
     */
    @Override
	protected Serializable getSessionId(ServletRequest request, ServletResponse
            response) {
        //获取请求头Authorization中的数据
        String id = WebUtils.toHttp(request).getHeader("Authorization");
        if (StringUtils.isEmpty(id)) {
            //如果没有携带,生成新的sessionId
            return super.getSessionId(request, response);
        } else {
            //返回sessionId;
            //从请求头获取SessionId
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            //sessionId的值是什么
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            //sessionId需不需要验证   
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        }
    }
}	

修改Shiro配置,配置Shiro基于redis的会话管理

	@Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private int port;
    @Value("${redis.dataBase}")
    private int dataBase;
    @Value("${redis.password}")
    private String password;
	
	//配置shiro的RedisManager,通过shiro-redis包提供的RedisManager统一对redis操作
	@Bean
	  public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host + ":" + port);
        if (StringUtils.isNotBlank(password)) {
            redisManager.setPassword(password);
        }
        redisManager.setDatabase(dataBase);
        return redisManager;
    }
	
	//配置Shiro的缓存管理器,替换Shiro内部自己的本地缓存机制,全部替换redis实现
	@Bean
	public RedisCacheManager cacheManager() {
	RedisCacheManager redisCacheManager = new RedisCacheManager();
	redisCacheManager.setRedisManager(redisManager());
	return redisCacheManager;
	}
	
	
	//配置SessionDao,使用shiro-redis基于redis的sessionDao的实现
	@Bean
	public RedisSessionDAO redisSessionDAO() {
	  RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
	  redisSessionDAO.setRedisManager(redisManager());
	  return redisSessionDAO;
	}
	
	  //配置会话管理器,指定sessionDao的依赖关系
	  @Bean
	  public DefaultWebSessionManager sessionManager() {
	    CustomSessionManager sessionManager = new CustomSessionManager();
	    sessionManager.setSessionDAO(redisSessionDAO());
	    return sessionManager;
	 }

注册自定义session管理器与自定义缓存管理器

	//配置安全管理器
	  @Bean
	  public SecurityManager securityManager(CustomRealm realm) {
	    DefaultWebSecurityManager securityManager = newDefaultWebSecurityManager();
	      // 自定义session管理注册到安全管理器中
    	securityManager.setSessionManager(sessionManager());
    	// 自定义缓存实现注册到安全管理器中
   		securityManager.setCacheManager(cacheManager());
	    securityManager.setRealm(realm);
	    return securityManager;
	 }

根据Authorization获取当前登录用户的信息

@Autowired
    private RedisSessionDAO redisSessionDAO;

	@RequestMapping("/getUser")
	public User getUserByHeader(ServletRequest request) throws Exception{
		//前端请求的headers中必须传入Authorization头信息
		String id = WebUtils.toHttp(request).getHeader("Authorization");
		Session session = redisSessionDAO.readSession(id);
		Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
		SimplePrincipalCollection simplePrincipalCollection = (SimplePrincipalCollection) obj;
		String userStr = JSON.toJSON(simplePrincipalCollection.getPrimaryPrincipal()).toString();
		User user = JSON.parseObject(userStr, User.class);
        return user;
	}

5.在线人数统计与强制用户下线

sessionManager管理器

@Bean
public SessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    Collection<SessionListener> listeners = new ArrayList<SessionListener>();
    listeners.add(new ShiroSessionListener());
    sessionManager.setSessionListeners(listeners);
    sessionManager.setSessionDAO(sessionDAO());
    return sessionManager;
}

ShiroSessionListener会话监听器

ShiroSessionListener为org.apache.shiro.session.SessionListener接口的实现,维护着一个原子类型的Integer对象,用于统计在线Session的数量

public class ShiroSessionListener implements SessionListener{
    private final AtomicInteger sessionCount = new AtomicInteger(0);
    
    @Override
    public void onStart(Session session) {
        sessionCount.incrementAndGet();
    }
    
    @Override
    public void onStop(Session session) {
        sessionCount.decrementAndGet();
    }
    
    @Override
    public void onExpiration(Session session) {
        sessionCount.decrementAndGet();
    }
}

在线用户UserOnline

public class UserOnline implements Serializable{
	
  /**
	 * session id
	 */
    private String id;
	/**
	 * 用户id
	 */
	private String userId;
	/**
	 * 用户名称
	 */
	private String username;
	/**
	 * 用户主机地址
	 */
    private String host;
	/**
	 * 登录IP
	 */
    private String systemHost;
	/**
	 * 状态
	 */
    private String status;
	/**
	 * session创建时间
	 */
    private Date startTimestamp;
	/**
	 * session最后访问时间
	 */
    private Date lastAccessTime;
	/**
	 * 超时时间
	 */
    private Long timeout;

}
public interface SessionService {

	/**
	 * 在线用户
	 * @return
	 */
	List<UserOnline> list();

	/**
	 * 强制用户下线
	 * @param sessionId
	 * @return
	 */
	boolean compulsoryLogout(String sessionId);
}
@Service("sessionService")
public class SessionServiceImpl implements SessionService {

	@Autowired
	private RedisSessionDAO redisSessionDAO;

	@Override
	public List<UserOnline> list() {
		List<UserOnline> list = new ArrayList<>();
		UserOnline userOnline ;
		//获取所有有效的Session
		Collection<Session> sessions = redisSessionDAO.getActiveSessions();
		for (Session session : sessions) {
			userOnline=new UserOnline();
			User user = new User();
			SimplePrincipalCollection principalCollection = new SimplePrincipalCollection();

			if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
				continue;
			} else {
				// 通过session获取用户信息。
				principalCollection = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
				user = (User) principalCollection.getPrimaryPrincipal();
				userOnline.setUsername(user.getUserName());
				userOnline.setUserId(user.getId().toString());
			}
			userOnline.setId((String) session.getId());
			userOnline.setHost(session.getHost());
			userOnline.setStartTimestamp(session.getStartTimestamp());
			userOnline.setLastAccessTime(session.getLastAccessTime());

			Long timeout = session.getTimeout();
			// 用户被踢出后Session Time置为0
			if (timeout == 0L) {
				userOnline.setStatus("离线");
			} else {
				userOnline.setStatus("在线");
			}
			userOnline.setTimeout(timeout);
			list.add(userOnline);
		}
		return list;
	}

	@Override
	public boolean compulsoryLogout(String sessionId) {
		Session session = redisSessionDAO.readSession(sessionId);
		session.setTimeout(0);
		return true;
	}

}

六、Shiro之Shiro标签

以在Thymeleaf使用Shiro标签为例

首先Thymeleaf官方并没有提供Shiro的标签,需要引入第三方实现(https://github.com/theborakompanioni/thymeleaf-extras-shiro)

1.引入依赖

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

2.修改Shiro配置

在ShiroConfig中配置该方言标签

@Bean
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}

3.HTML页面

在html页面中使用Shiro标签需要给html标签添加xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

<!DOCTYPE html>
<html
        xmlns:th="http://www.thymeleaf.org"
        xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<p>你好![[${user.userName}]],欢迎登陆</p>
<p shiro:hasRole="user">用户管理角色</p>
<p shiro:hasRole="test">测试人员角色</p>
<div>
    <a shiro:hasPermission="user:add" th:href="@{/user/add}">新增用户</a>
    <a shiro:hasPermission="user:delete" th:href="@{/user/delete}">删除用户</a>
</div>
</body>
</html>

七、Shiro之JWT

Shiro是基于session保持会话的,是有状态的。

JWT则是无状态的,服务端不保存session,而是生成token发送给客户端进行保存,之后的所有的请求都需要携带token,再对token进行解析判断

添加依赖

<!--    <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>-->
        
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1</version>
        </dependency>

定义JWTUtil

public class JWTUtil {

    /**
     * 指定一个token过期时间(毫秒)
     */
    private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;

    /**
     * 生成 token
     *
     * @param username
     * @param secret
     * @return token
     */
    public static String sign(String username, String secret) {
        try {
            //统一字符串转小写
            username = StringUtils.lowerCase(username);

            // 过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);

            // 传入在验证或签名实例中使用的密匙,得到HMAC256算法
            Algorithm algorithm = Algorithm.HMAC256(secret);

            // 附带username信息的token
            return JWT.create()
                    .withClaim("username", username)
                    //过期时间
                    .withExpiresAt(date)
                    //签名算法
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 校验token是否正确
     *
     * @param token
     * @param secret
     * @return
     */
    public static boolean verify(String token, String secret) {
        try {
            // 传入在验证或签名实例中使用的密匙,得到HMAC256算法
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 提供算法得到带有用于验证令牌签名的算法的JWTVerifier构建器
            JWTVerifier verifier = JWT.require(algorithm).build();
            //效验TOKEN
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 在token中获取username信息
     *
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 判断是否过期
     */
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().getTime() < System.currentTimeMillis();
    }
}

定义JWTToken

将Jwt与Shiro对接,定义JWTToken,将令牌封装成认证对象,shiro会拦截所有请求,然后验证请求提交的Token是否合法

/**
 * 自定义shiro接口token,通过这个类将string的token转型成AuthenticationToken,供shiro使用
 * 
 * 需要重写getPrincipal和getCredentials方法
 */
public class JWTToken implements AuthenticationToken {

    private String token;

    private String exipreAt;

    public JWTToken(String token) {
        this.token = token;
    }

    public JWTToken(String token, String exipreAt) {
        this.token = token;
        this.exipreAt = exipreAt;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getExipreAt() {
        return exipreAt;
    }

    public void setExipreAt(String exipreAt) {
        this.exipreAt = exipreAt;
    }
}

自定义Realm

public class ShiroRealm extends AuthorizingRealm {

    /**
     * 限定这个realm只能处理JwtToken
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * `
     * 授权模块,获取用户角色和权限
     *
     * @param token token
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
        String username = JWTUtil.getUsername(token.toString());

        // TODO 操作数据库获取角色权限信息
        List<User> users = new ArrayList<>();
        User user1 = new User("admin", "a18d2133f593d7b0e3ed488560404083", new HashSet<>(Collections.singletonList("admin")), new HashSet<>(Arrays.asList("user:add", "user:delete")));
        User user2 = new User("test", "a18d2133f593d7b0e3ed488560404083", new HashSet<>(Collections.singletonList("test")), new HashSet<>(Arrays.asList("user:delete")));
        users.add(user1);
        users.add(user2);

        User user = users.stream().filter(u -> StringUtils.equalsIgnoreCase(username, u.getUsername())).findFirst().orElse(null);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 获取用户角色集
        simpleAuthorizationInfo.setRoles(user.getRole());

        // 获取用户权限集
        simpleAuthorizationInfo.setStringPermissions(user.getPermission());
        return simpleAuthorizationInfo;
    }

    /**
     * 用户认证
     *
     * @param authenticationToken 身份认证 token
     * @return AuthenticationInfo 身份认证信息
     * @throws AuthenticationException 认证相关异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // token是从JWTFilter的executeLogin方法传递过来的,已经经过解密,是JwtToken中重写这个方法
        String token = (String) authenticationToken.getCredentials();

        // 获得username
        String username = JWTUtil.getUsername(token);

        if (StringUtils.isBlank(username)) {
            throw new AuthenticationException("token校验不通过");
        }

        //TODO 数据操作查询用户信息
        List<User> users = new ArrayList<>();
        // 模拟两个用户:
        // 1. 用户名 admin,密码 123456,角色 admin(管理员),权限 "user:add","user:view"
        // 1. 用户名 scott,密码 123456,角色 regist(注册用户),权限 "user:view"
        users.add(new User(
                "admin",
                "a18d2133f593d7b0e3ed488560404083",
                new HashSet<>(Collections.singletonList("admin")),
                new HashSet<>(Arrays.asList("user:add", "user:view"))));
        users.add(new User(
                "test",
                "a18d2133f593d7b0e3ed488560404083",
                new HashSet<>(Collections.singletonList("regist")),
                new HashSet<>(Collections.singletonList("user:view"))));

        User user = users.stream().filter(u -> StringUtils.equalsIgnoreCase(username, u.getUsername())).findFirst().orElse(null);

        if (user == null) {
            throw new AuthenticationException("用户名或密码错误");
        }
        if (!JWTUtil.verify(token, username, user.getPassword())) {
            throw new AuthenticationException("token校验不通过");
        }

        //toke过期
        if (JWTUtil.isExpire(token)) {
            throw new ExpiredCredentialsException("toke过期");
        }
//        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes("salt"), "shiro_realm");
        return new SimpleAuthenticationInfo(token, token, "shiro_realm");
    }
}

定义JWTFilter

/**
 * jwt过滤器,作为shiro的过滤器,对请求进行拦截并处理
 * <p>
 * 跨域配置
 */
public class JWTFilter extends BasicHttpAuthenticationFilter {

    private static final String TOKEN = "Token";

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * 过滤器拦截请求的入口方法
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws UnauthorizedException
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {

        // Shiro配置中添加过滤

//        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//        String url="/user/register,/user/login";
//        String[] anonUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(url, ",");
//
//        boolean match = false;
//        for (String u : anonUrl) {
//            if (pathMatcher.match(u, httpServletRequest.getRequestURI())) {
//                match = true;
//            }
//        }
//        if (match) {
//            return true;
//        }

        if (isLoginAttempt(request, response)) {
            try {
                //token验证
                return executeLogin(request, response);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 如果传入请求是基于登录的尝试,则为 true,否则为 false
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("尝试登录");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(TOKEN);
        return token != null;
    }

    /**
     * 进行token的验证
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        //在请求头中获取token
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //前端命名Token
        String token = httpServletRequest.getHeader(TOKEN);
        //token不存在
        if (token == null || "".equals(token)) {
            this.responseResult(response, "0", "无token,无权访问,请先登录");
            return false;
        }
        //token存在,进行验证
        JWTToken jwtToken = new JWTToken(token);
        try {
            //通过subject,提交给Realm进行登录验证
            //SecurityUtils.getSubject().login(jwtToken);
            getSubject(request, response).login(jwtToken);
            return true;
        } catch (ExpiredCredentialsException e) {
            this.responseResult(response, "0", "token过期,请重新登录");
            return false;
        } catch (ShiroException e) {
            this.responseResult(response, "0", "token被伪造,无效token");
            return false;
        }
    }

    /**
     * isAccessAllowed()方法返回false,进入onAccessDenied方法
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("验证不通过,进入onAccessDenied()");
        return false;
    }


    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域会先发送一个option请求,让option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }


    /**
     * json形式返回结果token验证失败信息
     */
    private void responseResult(ServletResponse response, String code, String msg) throws IOException {
        HashMap<String, Object> map = new HashMap<>();
        map.put("msg", msg);
        map.put("code", code);

        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        ObjectMapper mapper = new ObjectMapper();
        String jsonRes = mapper.writeValueAsString(map);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        httpServletResponse.getOutputStream().write(jsonRes.getBytes());
    }
}

Shiro Config

@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilter(用于拦截所有请求,对受限资源进行Shiro的认证和授权判断)
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 在Shiro过滤器链上加入JWTFilter,添加自己的过滤器并且取名为jwt
        LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filters);

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //登录路径、注册路径都需要放行不进行拦截
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/user/register", "anon");
        // 所有请求都要经过 jwt过滤器
        filterChainDefinitionMap.put("/**", "jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //给安全管理器设置realm
        securityManager.setRealm(shiroRealm());

        //关闭shiro的session(无状态的方式使用shiro)
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    /**
     * 加密
     * 使用了UsernamePasswordToken并且有对password进行加密的才需要
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用md5 算法进行加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置散列次数: 意为加密几次
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }


    /**
     * 配置 Realm
     *
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
        //使用了UsernamePasswordToken并且有对password进行加密的才需要
        //realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    /**
     * 开启对 Shiro 注解的支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * 强制使用cglib,防止重复代理和可能引起代理出错的问题
     *
     * 使用shiro-spring-boot-starter遇到使用shiro注解导致controller类中spring注解失效,需使用以下配置
     *
     * 使用shiro-spring则不需要使用以下配置
     * @return
     */
//    @Bean
//    @DependsOn({"lifecycleBeanPostProcessor"})
//    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
//        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
//        advisorAutoProxyCreator.setProxyTargetClass(true);
//        return advisorAutoProxyCreator;
//    }
}

执行测试

@RestController
@RequestMapping("/user")
public class LoginController {

    @PostMapping("/login")
    public Object login(String username, String password) throws Exception {

        // UsernamePasswordToken认证方式
//        Subject subject = SecurityUtils.getSubject();
//        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//        subject.login(token);


        // JWT认证方式

        //TODO 用户验证是否存在
        username = StringUtils.lowerCase(username);
        //盐 + 密码 + 1024次散列,作为token生成的密钥
        Md5Hash md5Hash = new Md5Hash(password, "salt", 1024);
        //toHex转换成16进制,32为字符
        password = md5Hash.toHex();

        // 用户验证成功生成 Token
        String token = JWTUtil.sign(username, password);
        //转换成jwtToken(才可以被shiro识别)
        JWTToken jwtToken = new JWTToken(token);

        //拿到Subject对象
        Subject subject = SecurityUtils.getSubject();

        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("token", null);
        userInfo.put("user", "user");

        //正式进行认证验证
        try {
            subject.login(jwtToken);
            userInfo.put("token", token);
            userInfo.put("msg", "登录成功");
        } catch (UnknownAccountException e) {
            userInfo.put("msg", "无效用户,用户不存在");
            e.printStackTrace();
        } catch (IncorrectCredentialsException e) {
            userInfo.put("msg", "密码输入错误");
            e.printStackTrace();
        } catch (ExpiredCredentialsException e) {
            userInfo.put("msg", "token过期,请重新登录");
            e.printStackTrace();
        }
        return userInfo;
    }

    /**
     * 需要登录才能访问
     */
    @GetMapping("/test1")
    public String test1() {
        return "success";
    }

    /**
     * 需要 admin 角色才能访问
     */
    @GetMapping("/test2")
    @RequiresRoles("admin")
    public String test2() {
        return "success";
    }

    /**
     * 需要 "user:add" 权限才能访问
     */
    @GetMapping("/test3")
    @RequiresPermissions("user:add")
    public String test3() {
        return "success";
    }

}

{
	"msg": "登录成功",
	"user": "user",
	"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjkzNTI3MzUsInVzZXJuYW1lIjoidGVzdCJ9.nBrTh6SbJocT14R-nh4VhQlaAyqh2J-ebTuk1Mj9AVY"
}

令牌自动刷新

定义ThreadLocalToken存储令牌

@Component
public class ThreadLocalToken {
    private ThreadLocal<String> local = new ThreadLocal<>();

    public void setToken(String token) {
        local.set(token);
    }

    public String getToken() {
        return local.get();
    }

    public void clear() {
        local.remove();
    }
}

定义JWTFilter 继承AuthenticatingFilter重写部分方法

@Component
// 使用threadLocalToken,所以该类应该是多例
@Scope("prototype")
public class JWTFilter extends AuthenticatingFilter {
    /**
     * 每个请求线程都有自己独立的threadLocalToken
     */
    @Autowired
    private ThreadLocalToken threadLocalToken;

    @Value("${zd.jwt.cache-expire}")
    private int cacheExpire;

    @Value("${zd.jwt.secret}")
    private String secret;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 拦截请求之后,用于把令牌字符串封装成令牌对象
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        // 获取请求token
        String token = getRequestToken(req);
        if (StrUtil.isBlank(token)) {
            return null;
        }
        return new JWTToken(token);
    }

    /**
     * 拦截请求,判断请求是否需要被shiro处理
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        // 放行Options请求
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return false;
    }

    /**
     * 处理所有应该被shiro处理的请求
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));

        threadLocalToken.clear();

        String token = getRequestToken(req);
        if (StrUtil.isBlank(token)) {
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().print("无效的令牌");
            return false;
        }
        try {
            JWTUtil.verify(token, secret);
        } catch (TokenExpiredException e) {
            if (redisTemplate.hasKey(token)) {
                redisTemplate.delete(token);
                String userName = JWTUtil.getUsername(token);
                token = JWTUtil.sign(userName, secret);
                redisTemplate.opsForValue().set(token, userName, cacheExpire, TimeUnit.DAYS);
                threadLocalToken.setToken(token);
            } else {
                resp.setStatus(HttpStatus.UNAUTHORIZED.value());
                resp.getWriter().print("令牌已过期");
                return false;
            }
        } catch (Exception e) {
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().print("无效的令牌");
            return false;
        }
        boolean bool = executeLogin(request, response);
        return bool;
    }

	/**
     * 登录失败
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
        resp.setStatus(HttpStatus.UNAUTHORIZED.value());
        try {
            resp.getWriter().print(e.getMessage());
        } catch (Exception exception) {

        }

        return false;
    }

    @Override
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
        super.doFilterInternal(request, response, chain);

    }
    
	/**
     * 获取请求Token
     * @param request
     * @return
     */
    private String getRequestToken(HttpServletRequest request) {
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }
        return token;
    }
}

利用AOP向客户端返回令牌信息

@Aspect
@Component
public class TokenAspect {
    @Autowired
    private ThreadLocalToken threadLocalToken;

    @Pointcut("execution(public * cn.ybzy.demo.controller.*.*(..))")
    public void aspect() {

    }

    @Around("aspect()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HashMap<String, Object> map = (HashMap<String, Object>) point.proceed();
        String token = threadLocalToken.getToken();
        if (token != null) {
            map.put("token", token);
            threadLocalToken.clear();
        }
        return map;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeDevMaster

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值