第十章 Springboot+ Shiro +Jwt前后端分离鉴权
1、前后端分离会话问题
【1】问题追踪
前面我们实现分布式的会话缓存,但是我们发现此功能的实现是基于浏览的cookie机制,也就是说用户禁用cookie后,我们的系统会就会产生会话不同的问题
【2】解决方案
我们的前端可能是web、Android、ios等应用,同时我们每一个接口都提供了无状态的应答方式,这里我们提供了基于JWT的token生成方案
1、用户登陆之后,获得此时会话的sessionId,使用JWT根据sessionId颁发签名并设置过期时间(与session过期时间相同)返回token
2、将token保存到客户端本地,并且每次发送请求时都在 header 上携带JwtToken
3、ShiroSessionManager继承DefaultWebSessionManager,重写getSessionId方法,从header上检测是否携带JwtToken,如果携带,则进行解码JwtToken,使用JwtToken中的jti作为SessionId。
4、重写shiro的默认 过滤器 ,使其支持jwtToken有效期校验、及对JSON的返回支持
JwtAuthc filter :实现是否需要登录的过滤,拒绝时如果header上携带JwtToken,则返回对应json
JwtPermsFilter:实现是否有对应资源的过滤,拒绝时如果header上携带JwtToken,则返回对应json
JwtRolesFilter:实现是否有对应角色的过滤,拒绝时如果header上携带JwtToken,则返回对应json
2、JWT概述
JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个 字符串 。
- 广义上:JWT是一个标准的名称;
- 狭义上:JWT指的就是用来传递的那个token字符串
JWT由三部分构成:header(头部)、payload(载荷)和signature(签名)。
- Header
存储两个变量
- 秘钥(可以用来比对)
- 算法(也就是下面将Header和payload加密成Signature)
- payload
存储很多东西,基础信息有如下几个
- 签发人,也就是这个“令牌”归属于哪个用户。一般是userId
- 创建时间,也就是这个令牌是什么时候创建的
- 失效时间,也就是这个令牌什么时候失效(session的失效时间)
- 唯一标识,一般可以使用算法生成一个唯一标识(jti==>sessionId)
- Signature
这个是上面两个经过Header中的算法加密生成的,用于比对信息,防止篡改Header和payload
然后将这三个部分的信息经过加密生成一个JwtToken的字符串,发送给客户端,客户端保存在本地。当客户端发起请求的时候携带这个到服务端(可以是在cookie,可以是在header),在服务端进行验证,我们需要解密对于的payload的内容
3、集成JWT
【1】JwtProperties
用于支持yaml文件配置的配置类
package com.itheima.shiro. config ;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* @Description:jw配置文件
*/@Data
@ConfigurationProperties(prefix = "itheima.framework.jwt")
public class JwtProperties implements Serializable {
/**
* @Description 签名密码
*/ private String hexEncodedSecretKey;
}
【2】JwtTokenManager
负责令牌的颁发、解析、校验
package com.itheima.shiro.core.impl;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.itheima.shiro.config.JwtProperties;
import com.itheima.shiro.utils.EncodesUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory. annotation .Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Hash Map ;
import java.util.Map;
import java.util.UUID;
@Service("jwtTokenManager")
@EnableConfigurationProperties({JwtProperties.class})
public class JwtTokenManager {
@Autowired
JwtProperties jwtProperties;
/**
* @Description 签发令牌
* jwt字符串包括三个部分
* 1. header
* -当前字符串的类型,一般都是“JWT”
* -哪种算法加密,“HS256”或者其他的加密算法
* 所以一般都是固定的,没有什么变化
* 2. payload
* 一般有四个最常见的标准字段(下面有)
* iat:签发时间,也就是这个jwt什么时候生成的
* jti:JWT的唯一标识
* iss:签发人,一般都是username或者userId
* exp:过期时间
* @param iss 签发人
* @param ttlMillis 有效时间
* @param claims jwt中存储的一些非隐私信息
* @return
*/ public String IssuedToken(String iss, long ttlMillis,String sessionId, Map<String, Object> claims) {
if (claims == null) {
claims = new HashMap<>();
}
long nowMillis = System.currentTimeMillis();
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(sessionId)//2. 这个是JWT的唯一标识,一般设置成唯一的,这个方法可以生成唯一标识,此时存储的为sessionId,登录成功后回写
.setIssuedAt(new Date(nowMillis))//1. 这个地方就是以毫秒为单位,换算当前系统时间生成的iat
.setSubject(iss)//3. 签发人,也就是JWT是给谁的(逻辑上一般都是username或者userId)
.signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey);//这个地方是生成jwt使用的算法和秘钥
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);//4. 过期时间,这个也是使用毫秒生成的,使用当前时间+前面传入的持续时间生成
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* @Description 解析令牌
* @param jwtToken 令牌
* @return
*/ public Claims decodeToken(String jwtToken) {
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
// 得到 DefaultJwtParser
return Jwts.parser()
// 设置签名的秘钥
.setSigningKey(base64EncodedSecretKey)
// 设置需要解析的 jwt
.parseClaimsJws(jwtToken)
.getBody();
}
/**
* @Description 判断令牌是否合法
* @param jwtToken 令牌
* @return
*/ public boolean isVerifyToken(String jwtToken) {
String base64EncodedSecretKey = EncodesUtil.encodeHex(jwtProperties.getBase64EncodedSecretKey().getBytes());
//这个是官方的校验规则,这里只写了一个”校验算法“,可以自己加
Algorithm algorithm = Algorithm.HMAC256(EncodesUtil.decodeBase64(base64EncodedSecretKey));
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwtToken); // 校验不通过会抛出异常
//判断合法的标准:1. 头部和荷载部分没有篡改过。2. 没有过期
return true;
}
}
4、重写DefaultWebSessionManager
ShiroSessionManager主要是添加jwtToken的jti作为会话的唯一标识
package com.itheima.shiro.core.impl;
import com.itheima.shiro.utils.EmptyUtil;
import io.jsonwebtoken.Claims;
import org. apache .shiro.web. servlet .ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Description 重写Jwt会话管理
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "jwtToken";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public ShiroSessionManager(){
super();
}
@Autowired
JwtTokenManager jwtTokenManager;
@ Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String jwtToken = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(EmptyUtil.isNullOrEmpty(jwtToken)){
//如果没有携带id参数则按照父类的方式在cookie进行获取
return super.getSessionId(request, response);
}else{
//如果请求头中有 authToken 则其值为jwtToken,然后解析出会话session
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
Claims decode = jwtTokenManager.decodeToken(jwtToken);
String id = (String) decode.get("jti");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return id;
}
}
}
5、重写默认过滤器
BaseResponse返回统一json的对象
package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.ToString;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Description 基础返回封装
*/@Data
public class BaseResponse extends ToString {
private Integer code ;
private String msg ;
private String date;
private static final long serialVersionUID = -1;
public BaseResponse(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public BaseResponse(Integer code, String msg, String date) {
this.code = code;
this.msg = msg;
this.date = date;
}
}
【1】JwtAuthcFilter
使用wtTokenManager.isVerifyToken(jwtToken)校验颁发jwtToken是否合法,同时在拒绝的时候返回对应的json数据格式
package com.itheima.shiro.core.filter;
import com.alibaba. fastjson .JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.core.impl.JwtTokenManager;
import com.itheima.shiro.core.impl.ShiroSessionManager;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @Description:自定义登录验证过滤器
*/public class JwtAuthcFilter extends FormAuthenticationFilter {
private JwtTokenManager jwtTokenManager;
public JwtAuthcFilter(JwtTokenManager jwtTokenManager) {
this.jwtTokenManager = jwtTokenManager;
}
/**
* @Description 是否允许访问
*/ @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:走jwt校验
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
boolean verifyToken = jwtTokenManager.isVerifyToken(jwtToken);
if (verifyToken){
return super.isAccessAllowed(request, response, mappedValue);
}else {
return false;
}
}
//没有没有:走原始校验
return super.isAccessAllowed(request, response, mappedValue);
}
/**
* @Description 访问拒绝时调用
*/ @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_LOGIN_CODE,ShiroConstant.NO_LOGIN_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
【2】JwtPermsFilter
package com.itheima.shiro.core.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @Description:自定义jwt的资源校验
*/public class JwtPermsFilter extends PermissionsAuthorizationFilter {
/**
* @Description 访问拒绝时调用
*/ @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_AUTH_CODE,ShiroConstant.NO_AUTH_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
【3】JwtRolesFilter
package com.itheima.shiro.core.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @Description:自定义jwt角色校验
*/public class JwtRolesFilter extends RolesAuthorizationFilter {
/**
* @Description 访问拒绝时调用
*/ @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
//判断当前请求头中是否带有jwtToken的字符串
String jwtToken = WebUtils.toHttp(request).getHeader("jwtToken");
//如果有:返回json的应答
if (!EmptyUtil.isNullOrEmpty(jwtToken)){
BaseResponse baseResponse = new BaseResponse(ShiroConstant.NO_ROLE_CODE,ShiroConstant.NO_ROLE_MESSAGE);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(baseResponse));
return false;
}
//如果没有:走原始方式
return super.onAccessDenied(request, response);
}
}
6、重写ShiroConfig
1、ShiroSessionManager替换DefaultWebSessionManager
2、生效过滤器
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.*;
import com.itheima.shiro.filter.*;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description 权限配置类
*/@Configuration
@ComponentScan(basePackages = {"com.itheima.shiro.core"})
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
@Autowired
JwtTokenManager jwtTokenManager;
/**
* @Description redission客户端
*/ @Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/ @Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 缓存管理器
* @param
* @return
*/ @Bean(name="shiroCacheManager")
public ShiroCacheManager shiroCacheManager(){
return new ShiroCacheManager();
}
/**
* @Description 权限管理器
* @param
* @return
*/ @Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
securityManager.setCacheManager(shiroCacheManager());
return securityManager;
}
/**
* @Description 自定义RealmImpl
*/ @Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的
*/ @Bean("redisSessionDao")
public SessionDAO redisSessionDao(){
RedisSessionDao sessionDAO = new RedisSessionDao();
sessionDAO.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionDAO;
}
/**
* @Description 会话管理器
*/ @Bean(name="sessionManager")
public ShiroSessionManager shiroSessionManager(){
ShiroSessionManager sessionManager = new ShiroSessionManager();
sessionManager.setSessionDAO(redisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(shiroRedisProperties.getGlobalSessionTimeout());
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/ @Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/ @Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/ @Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/ private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description 自定义过滤器定义
*/ private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
map.put("kicked-out", new KickedOutAuthorizationFilter(redissonClient(), redisSessionDao(), shiroSessionManager()));
map.put("jwt-authc", new JwtAuthcFilter(jwtTokenManager));
map.put("jwt-perms", new JwtPermsFilter());
map.put("jwt-roles", new JwtRolesFilter());
return map;
}
/**
* @Description Shiro过滤器
*/ @Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
7、业务代码
【1】LoginAction
添加LoginForJwt方法
/**
* @Description jwt的json登录方式
* @param loginVo
* @return
*/ @RequestMapping("login-jwt")
@ResponseBody
public BaseResponse LoginForJwt(@RequestBody LoginVo loginVo){
return loginService.routeForJwt(loginVo);
}
【2】LoginService
添加routeForJwt方法
package com.itheima.shiro.service;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.vo.LoginVo;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import java.util.Map;
/**
* @Description 登陆业务接口
*/
public interface LoginService {
/**
* @Description 登陆路由
* @param loginVo 登录参数
* @return
*/ public Map<String, String> route(LoginVo loginVo) throws UnknownAccountException,IncorrectCredentialsException;
/**
* @Description jwt方式登录
@param loginVo 登录参数
* @return
*/ public BaseResponse routeForJwt(LoginVo loginVo) throws UnknownAccountException,IncorrectCredentialsException;
}
【3】LoginServiceImpl
package com.itheima.shiro.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.constant.ShiroConstant;
import com.itheima.shiro.core.base.BaseResponse;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.core.impl.JwtTokenManager;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.service.LoginService;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.ShiroUserUtil;
import com.itheima.shiro.utils.ShiroUtil;
import com.itheima.shiro.vo.LoginVo;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description 登陆业务实现
*/@Service("loginService")
@Log4j2
public class LoginServiceImpl implements LoginService {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
@Autowired
UserBridgeService userBridgeService;
@Autowired
JwtTokenManager jwtTokenManager;
/* (non-Javadoc)
* @see LoginService#route(com.yz.commons.vo.LoginVo)
*/ @Override
public Map<String, String> route(LoginVo loginVo) throws UnknownAccountException, IncorrectCredentialsException {
Map<String, String> map = new HashMap<>();
try {
SimpleToken token = new SimpleToken(null, loginVo.getLoginName(), loginVo.getPassWord());
Subject subject = SecurityUtils.getSubject();
subject.login(token);
//创建缓存
this.loadAuthorityToCache();
} catch (UnknownAccountException ex) {
log.error("登陆异常:{}", ex);
throw new UnknownAccountException(ex);
} catch (IncorrectCredentialsException ex) {
log.error("登陆异常:{}", ex);
throw new IncorrectCredentialsException(ex);
}
return map;
}
@Override
public BaseResponse routeForJwt(LoginVo loginVo) throws UnknownAccountException, IncorrectCredentialsException {
Map<String, String> map = new HashMap<>();
String jwtToken = null;
try {
SimpleToken token = new SimpleToken(null, loginVo.getLoginName(), loginVo.getPassWord());
Subject subject = SecurityUtils.getSubject();
subject.login(token);
String shiroSessionId = ShiroUserUtil.getShiroSessionId();
//登录后颁发的令牌
ShiroUser shiroUser = ShiroUserUtil.getShiroUser();
Map<String, Object> claims = new HashMap<>();
claims.put("shiroUser", JSONObject.toJSONString(shiroUser));
jwtToken = jwtTokenManager.IssuedToken("system", subject.getSession().getTimeout(),shiroSessionId,claims);
map.put("jwtToken",jwtToken );
log.info("jwtToken:{}",map.toString());
//创建缓存
this.loadAuthorityToCache();
} catch (Exception ex) {
BaseResponse baseResponse = new BaseResponse(ShiroConstant.LOGIN_FAILURE_CODE, ShiroConstant.LOGIN_FAILURE_MESSAGE);
return baseResponse;
}
BaseResponse baseResponse = new BaseResponse(ShiroConstant.LOGIN_SUCCESS_CODE,ShiroConstant.LOGIN_SUCCESS_MESSAGE,jwtToken);
return baseResponse;
}
/**
*
* <b>方法名:</b>:loadAuthorityToCache<br>
* <b>功能说明:</b>:加载缓存<br>
*/ private void loadAuthorityToCache(){
//登陆成功后缓存用户的权限信息进入缓存
ShiroUser shiroUser = ShiroUserUtil.getShiroUser();
User user = BeanConv.toBean(shiroUser, User.class);
userBridgeService.loadUserAuthorityToCache(user);
}
}
【5】authentication.properties
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
#/resource/**=roleOr[MangerRole,SuperAdmin]
/role/** =jwt-roles[SuperAdmin]
/resource/** =jwt-perms[role:listInitialize]
#其他链接是需要登录的
/**=kicked-out,jwt-authc
8、测试
1、测试登录后,jwtToken的生成,且校验会话是否使用新的jwtToken里的会话jti
2、测试自定义过滤器是否生效
使用jay/pass登录
使用admin/pass登录