先讲一下大概步骤
用户登录后,返回jwtToken(token中保存了过期时间,比如5分钟)给用户,同时把token保存到redis中(设置一个自动删除时间,比如30分钟),然后用户带上这个Token访问其他接口,如果redis中没有这个token,则token已经失效、如果token已经超过5分钟(过期)而redis中的这个token还存在,则重新生成token返回给用户且更新redis中token这样token的时间就延长了5分钟。因为redis中也保存了token所以我们就可以实现T用户,统计在线用户等功能
首先添加第三方的jar包(代码在github上,就不贴了)
https://github.com/gemingyi/shiro_demo
一、JWT工具类(网上找的)
@Component
public class JWTUtil {
//token过期时间
private static String tokenExpireTime ;
@Value("${jwt.tokenExpireTime}")
public void setTokenExpireTime(String tokenExpireTime) {
JWTUtil.tokenExpireTime = tokenExpireTime;
}
/**
* 校验token是否正确
*
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static Map verify(String token, String username, String secret) {
Map result = new HashMap(2);
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("userName", username)
.build();
DecodedJWT jwt = verifier.verify(token);
result.put("isSuccess", true);
result.put("ex", null);
// return true;
} catch (Exception exception) {
result.put("isSuccess", false);
result.put("exception", exception);
// return false;
}
return result;
}
/**
* 获得token中的信息无需secret解密也能获得
*
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userName").asString();
} catch (JWTDecodeException e) {
e.printStackTrace();
return null;
}
}
/**
* 生成签名,30min后过期
*
* @param username 用户名
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(String username, String secret) {
try {
//token过期时间
Date date = new Date(System.currentTimeMillis() + (Long.parseLong(tokenExpireTime) * 60 * 1000));
//密码MD5加密
// Object md5Password = new SimpleHash("MD5", secret, username, 2);
// Algorithm algorithm = Algorithm.HMAC256(String.valueOf(md5Password));
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("userName", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Autowired
RedisTemplate redisTemplate;
/**
* 大坑!,必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 授权
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = JWTUtil.getUsername(principals.toString());
SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
Map map = null;
try {
map = this.userService.getRolesAndPermissionsByUserName(userName);
auth.setRoles((Set) map.get("allRoles"));
auth.setStringPermissions((Set) map.get("allPermissions"));
} catch (Exception e) {
e.printStackTrace();
}
return auth;
}
/**
* 认证
*
* @param auth
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials();
// 解密获得userName,用于和数据库进行对比
String userName = JWTUtil.getUsername(token);
User vo = this.userService.getUserByUserName(userName);
String redisUserInfo = (String) redisTemplate.opsForValue().get("token_jwt_" + userName);
Map result = JWTUtil.verify(token, userName, vo.getPassword());
Exception exception = (Exception) result.get("exception");
if (vo == null) {
throw new UnknownAccountException("该帐号不存在!");
} else if (vo.getLock() == null || vo.getLock().equals(1)) {
throw new UnknownAccountException("该帐号已被锁定!");
} else if (exception != null && exception instanceof SignatureVerificationException) {
throw new AuthenticationException("Token错误(Token incorrect.)!");
} else if (exception != null && exception instanceof TokenExpiredException) {
throw new AuthenticationException("Token已过期(Token expired.)!");
//被T
} else if(StringUtils.isEmpty(redisUserInfo)){
throw new AuthenticationException("Token已失效(Token invalid.)!");
}else {
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(token, token, vo.getUserName());
return authcInfo;
}
}
}
三、自定义filter,继承BasicHttpAuthenticationFilter
@Component
public class JWTFilter extends BasicHttpAuthenticationFilter {
@Value("${jwt.anonymous.urls}")
private String anonymousStr;
@Autowired
RedisTemplate redisTemplate;
@Autowired
private IUserService userService;
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String contextPath = WebUtils.getPathWithinApplication(WebUtils.toHttp(servletRequest));
if (!StringUtils.isEmpty(anonymousStr)) {
String[] anonUrls = anonymousStr.split(",");
//匿名可访问的url
for (int i = 0; i < anonUrls.length; i++) {
if (contextPath.contains(anonUrls[i])) {
return true;
}
}
}
//获取请求头token
AuthenticationToken token = this.createToken(servletRequest, servletResponse);
if (token.getPrincipal() == null) {
handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), CodeAndMsgEnum.UNAUTHENTIC.getMsg());
return false;
} else {
try {
this.getSubject(servletRequest, servletResponse).login(token);
return true;
} catch (Exception e) {
String msg = e.getMessage();
//token错误
if (msg.contains("incorrect")) {
handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), msg);
return false;
//token过期
} else if (msg.contains("expired")) {
//尝试刷新token
if (this.refreshToken(servletRequest, servletResponse)) {
return true;
} else {
handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), "token已过期,请重新登录");
return false;
}
}
handler401(servletResponse, CodeAndMsgEnum.UNAUTHENTIC.getcode(), msg);
return false;
}
}
}
/**
* 此处为AccessToken刷新,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问
*
* @param servletRequest
* @param servletResponse
* @return
*/
private boolean refreshToken(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取header,tokenStr
String oldToken = request.getHeader("Authorization");
String userName = JWTUtil.getUsername(oldToken);
String key = CommonConstant.JWT_TOKEN + userName;
//获取redis tokenStr
String redisUserInfo = (String) redisTemplate.opsForValue().get(key);
if (redisUserInfo != null) {
if (oldToken.equals(redisUserInfo)) {
User vo = this.userService.getUserByUserName(userName);
//重写生成token(刷新)
String newTokenStr = JWTUtil.sign(vo.getUserName(), vo.getPassword());
JWTToken jwtToken = new JWTToken(newTokenStr);
userService.addTokenToRedis(userName, newTokenStr);
SecurityUtils.getSubject().login(jwtToken);
response.setHeader("Authorization", newTokenStr);
return true;
}
}
return false;
}
/**
* token有问题
*
* @param response
* @param code
* @param msg
*/
private void handler401(ServletResponse response, int code, String msg) {
try {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpStatus.OK.value());
response.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().write("{\"code\":" + code + ", \"msg\":\"" + msg + "\"}");
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
/**
* 支持跨域
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Authorization,Origin,X-Requested-With,Content-Type,Accept");
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(httpServletRequest, httpServletResponse);
}
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader("Authorization");
return new JWTToken(token);
}
}
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
@Configuration
public class ShiroConfiguration {
@Bean(name = "myRealm")
public MyRealm myRealm() {
MyRealm myShiroRealm = new MyRealm();
// myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
myShiroRealm.setCachingEnabled(true);
// myShiroRealm.setCacheManager(redisCacheManager());
return myShiroRealm;
}
@Bean(name = "subjectFactory")
public StatelessDefaultSubjectFactory subjectFactory() {
StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
return statelessDefaultSubjectFactory;
}
@Bean(name = "sessionManager")
public DefaultSessionManager sessionManager() {
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
return sessionManager;
}
@Bean(name = "defaultSessionStorageEvaluator")
public DefaultSessionStorageEvaluator defaultSessionStorageEvaluator () {
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
return defaultSessionStorageEvaluator;
}
@Bean(name = "subjectDAO")
public DefaultSubjectDAO subjectDAO(@Qualifier("defaultSessionStorageEvaluator")DefaultSessionStorageEvaluator defaultSessionStorageEvaluator) {
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
return defaultSubjectDAO;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("myRealm")MyRealm myRealm, @Qualifier("subjectDAO")DefaultSubjectDAO
subjectDAO, @Qualifier("sessionManager")DefaultSessionManager sessionManager, @Qualifier("subjectFactory")StatelessDefaultSubjectFactory subjectFactory) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setSubjectDAO(subjectDAO);
securityManager.setSubjectFactory(subjectFactory);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean(name = "jwtFilter")
public JWTFilter jwtFilter() {
return new JWTFilter();
}
@Bean
public FilterRegistrationBean delegatingFilterProxy(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager, @Qualifier("jwtFilter")JWTFilter jwtFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//
Map filters = new HashedMap(2);
filters.put("jwtFilter", jwtFilter);
shiroFilterFactoryBean.setFilters(filters);
//拦截链
Map filterChainDefinitionMap = new LinkedHashMap();
filterChainDefinitionMap.put("/**", "jwtFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "advisorAutoProxyCreator")
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean(name = "authorizationAttributeSourceAdvisor")
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
控制层代码
@RequestMapping(value = "/userLogin", method = RequestMethod.POST)
public Map ajaxLogin(User userInfo, HttpServletResponse response) {
Map result = new HashMap<>(4);
User vo = this.userService.getUserByUserName(userInfo.getUserName());
if (null != vo && vo.getPassword().equals(userInfo.getPassword())) {
String tokenStr = JWTUtil.sign(userInfo.getUserName(), userInfo.getPassword());
userService.addTokenToRedis(userInfo.getUserName(), tokenStr);
result.put("code", CodeAndMsgEnum.SUCCESS.getcode());
result.put("msg", "登录成功!");
response.setHeader("Authorization", tokenStr);
} else {
result.put("code", CodeAndMsgEnum.ERROR.getcode());
result.put("msg", "帐号或密码错误!");
}
return result;
}