持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
1、概述
在上一篇的分享中,我们整合完 Shiro 是用的 Shiro 自己的 Session 实现用户的认证的。Session 是一种有状态的认证方式,服务端需要保存用户的状态、客户端请求依赖服务端。而 jwt 则是一种常见的无状态认证方式。
服务端不保存客户端的请求信息,在客户端进行第一次请求时,对用户信息进行认证,通过后将加密token返回给客户端,作为客户端的访问凭证。这就是无状态登录的一个主要流程。
JWT - Json Web Token,是一个开放标准(RFC7519),一个轻量级的JSON风格授权和身份认证规范。它主要由 Header、payload、signature 三部分组成。其中 Header 是头部信息,包括签名算法等信息;payload 包括用户信息、token的签发时间、签发人等;signature就是这个token的签名,用来校验token是否合法。
2、Shiro + Jwt 整合使用
核心就是将Shiro禁用自带的Session,使用我们自己生成的JwtToken进行认证、授权。
2.1、引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.12.1</version>
</dependency>
2.2、改造Shiro配置类
//1、禁用Shiro自带的Session
@Bean("securityManager")
public DefaultWebSecurityManager getManager(ShiroRealm realm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 设置realm
manager.setRealm(realm);
/*
* 关闭shiro自带的session
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
//2、在Shiro过滤器配置中增加JwtFilter
filterMap.put("jwt", new JWTFilter());
2.3、修改ShiroRealm
//1、修改认证逻辑,改为JwtToken
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials();
//token解密,得到用户信息
TokenUserInfo tuInfo = JWTUtils.tokenInfoByToken(token);
String userId = tuInfo.getUserId();
SysUser user = getUserById(userId);
String pwd = user.getPassword();
if (!JWTUtils.verify(token, tuInfo, pwd)) {
throw new AuthenticationException("用户名密码错误!");
}
return new SimpleAuthenticationInfo(user, token, "jwt_token");
}
2.4、增加JwtFilter
public class JWTFilter extends BasicHttpAuthenticationFilter {
/**
* 判断是否需要登录
* @param request
* @param response
* @return
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader(JWT.AUTH);
return authorization != null;
}
/**
* 执行login
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader(JWT.AUTH);
if (JWTUtils.loginExpire(authorization)) {
throw new AuthenticationException(expMsg);
}
if (JWTUtils.needRefresh(authorization)) {
authorization = refreshToken(request, response);
}
JWTToken token = new JWTToken(authorization);
Subject subject = getSubject(request, response);
subject.login(token);
return true;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest hRequest = (HttpServletRequest) request;
if (isLoginAttempt(request, response)) {
try {
boolean loginSuccess = executeLogin(request, response);
return loginSuccess;
} catch (Exception e) {
LOGGER.error(e);
}
}
return false;
}
private String refreshToken(ServletRequest request, ServletResponse response) throws Exception {
String token = this.getAuthzHeader(request);
TokenUserInfo tuInfo = JWTUtils.tokenUserInfoByToken(token);
SysUser user = authUserService.getUserById(tuInfo.getUserId());
String pwd = user.getPassword();
Algorithm algorithm = Algorithm.HMAC256(pwd);
JWTUtils.verifySign(algorithm, token);
String newToken = JWTUtils.sign(tuInfo, pwd);
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.addHeader("RefreshJwtToken", newToken);
return newToken;
}
}
好了,到此我们就愉快的将Shiro自带Session换成了JwtToken了。😎