shiro关于token的一些操作

此文集中3个点

  1. 怎么去实现验证token的拦截器
  2. 如何生成token

怎么去实现验证token的拦截器

查看shiro的文档 关于其拦截器https://www.w3cschool.cn/shiro/oibf1ifh.html

重写shiro的逻辑实现功能

image-20200626020149856

主要重写方法isAccessAllowed 所有请求将会进入此方法

参考文章https://blog.csdn.net/qq_33591200/article/details/82884304

image-20200626022619052

shiro的拦截器管理方法 shiroFilter

加入代码

1
2
//设置所有请求都需要进行过滤
filterChainMap.put("/*", "authcBasic");

拦截器实现流程:

对BasicHttpAuthenticationFilter进行重写得到需要的效果

在isAccessAllowed里 如果request里的头不带token 对其进行放行,后期会加上对指定url放行;

在login登录中 验证成功即创建一个token 返回给前端

前端获得正常返回值后 请求头都会带着token 前端代码就不放了 自行百度

前端在验证成功马上就会去请求用户数据 此时带着token进行请求

在拦截器isAccessAllowed 进入 验证方法 executeLogin

此时使用 subject.login时 使用自定义createtoken创建一个自定义AuthenticationToken

而login方法要进入自定义realm进行处理 所以我们需要 新增一个JwtRealm 对带着token的请求进行验证处理

重写onLoginSuccess 当token验证成功后 对token进行刷新

大致的就是这样

关于jwt的maven

1
2
3
4
5
6
<!--Token生成与解析-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>

关于shiro快速整合jwt可以尝试https://github.com/louislivi/fastdep

shiro配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.xia.zhb.shiro;

import com.xia.zhb.shiro.filter.JwtAuthTokenFilter;
import com.xia.zhb.shiro.realm.JwtRealm;
import com.xia.zhb.shiro.realm.MyRealm;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

@Slf4j
@Configuration
public class ShiroConfig {



/**
* 注入自定义的 Realm
* @return MyRealm
*/
@Bean
public MyRealm myAuthRealm() {
MyRealm myRealm = new MyRealm();
log.info("====myRealm注册完成=====");
return myRealm;
}

@Bean
public JwtRealm jwtAuthRealm() {
JwtRealm jwtRealm = new JwtRealm();
log.info("====jwtRealm注册完成=====");
return jwtRealm;
}


/**
* 配置 ModularRealmAuthenticator
*/
@Bean
public ModularRealmAuthenticator authenticator() {
ModularRealmAuthenticator authenticator = new MultiRealmAuthenticator();
// 设置多 Realm的认证策略,默认 AtLeastOneSuccessfulStrategy
AuthenticationStrategy strategy = new FirstSuccessfulStrategy();
authenticator.setAuthenticationStrategy(strategy);
return authenticator;
}



/**
* 注入安全管理器
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
// 将自定义 Realm 加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
// 1.Authenticator
securityManager.setAuthenticator(authenticator());

// 2.Realm
List<Realm> realms = new ArrayList<Realm>(16);
realms.add(jwtAuthRealm());
realms.add(myAuthRealm());
securityManager.setRealms(realms);

log.info("====securityManager注册完成====");
return securityManager;
}
/**
* 注入 Shiro 过滤器
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 定义 shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

// 设置自定义的 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 设置默认登录的 URL,身份认证失败会访问该 URL
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置成功之后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/success");
// 设置未授权界面,权限认证失败会访问该 URL
shiroFilterFactoryBean.setUnauthorizedUrl("/error");

// LinkedHashMap 是有序的,进行顺序拦截器配置
Map<String,String> filterChainMap = new LinkedHashMap<String,String>();

// 自定义过滤器
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put("authcBasic", new JwtAuthTokenFilter());

shiroFilterFactoryBean.setFilters(filterMap);

// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
// filterChainMap.put("/css/**", "anon");
// filterChainMap.put("/imgs/**", "anon");
// filterChainMap.put("/js/**", "anon");

filterChainMap.put("/static/static/**", "anon");
filterChainMap.put("/static/**", "anon");
filterChainMap.put("/swagger-*/**", "anon");
filterChainMap.put("/swagger-ui.html/**", "anon");
// 验证码放行
filterChainMap.put("/prod-api/captcha", "anon");
filterChainMap.put("/captcha", "anon");

// // 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
// filterChainMap.put("/user/admin*", "authc");
// // “/user/student” 开头的用户需要角色认证,是“admin”才允许
// filterChainMap.put("/user/student*/**", "roles[admin]");
// // “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
// filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
//
// // 配置 logout 过滤器
// filterChainMap.put("/logout", "logout");
//设置所有请求都需要进行过滤
filterChainMap.put("/*", "authcBasic");
filterChainMap.put("/**", "anon");
// 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
log.info("====shiroFilterFactoryBean注册完成====");
return shiroFilterFactoryBean;
}




}

拦截器代码

JwtAuthTokenFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.xia.zhb.shiro.filter;
import com.xia.zhb.controller.common.domain.AjaxResult;
import com.xia.zhb.shiro.JWTToken;
import com.xia.zhb.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
@Component
public class JwtAuthTokenFilter extends BasicHttpAuthenticationFilter {

@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //判断请求的请求头是否带上 "Token"

String token=JwtUtil.getToken(request);

log.info("token->"+ token);

if (token != null) {

//如果不为空,则进入 executeLogin 方法执行登入,检查 token 是否正确
try {

executeLogin(request, response);
return true;
} catch (Exception e) {
//token 错误
e.printStackTrace();
}

}
//如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
return true;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
/* if (tokenSubjectService == null) {//解决service为null无法注入问题
System.out.println("tokenSubjectService is null!!!");
BeanFactory factory = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
tokenSubjectService = (TokenSubjectService) factory
.getBean("tokenSubjectService");
}*/
AuthenticationToken token = createToken(request, response);

try {
Subject subject = getSubject(request, response);
subject.login(token); // 交给 Shiro 去进行登录验证
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}


// 如果没有抛出异常则代表登入成功,返回true

}
/**
* 从 Header 里提取 JWT token
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {

String token = JwtUtil.getToken(servletRequest);
JWTToken jwtToken = new JWTToken(token);
return jwtToken;
}



/**
* Shiro 利用 JWT token 登录成功,会进入该方法
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
String newToken = null;
if (token instanceof JWTToken) {
newToken = JwtUtil.refreshTokenExpired((String) token.getCredentials());
}
if (newToken != null)
httpResponse.setHeader(JwtUtil.header, newToken);
return true;
}

/**
* Shiro 利用 JWT token 登录失败,会进入该方法
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
ServletResponse response) {
// 此处直接返回 false ,交给后面的 onAccessDenied()方法进行处理
return false;
}


@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//throw ExceptionUtil.NOLOGIN;
//return false;

HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter writer = httpResponse.getWriter();
AjaxResult ajaxResult=AjaxResult.error("token不合法");
writer.write(ajaxResult.toString());

return false;

}
private void authenticationFail(ServletRequest req, ServletResponse resp) {
try {
String loginUrl = getLoginUrl();
WebUtils.issueRedirect(req, resp, loginUrl);
} catch (IOException e) {
log.error(e.getMessage());
}
}


}

通过重写createToken方法 在处理非login请求被拦截时 构建一个自定义的token

JWTToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.xia.zhb.shiro;

import lombok.Data;

import org.apache.shiro.authc.AuthenticationToken;
@Data
public class JWTToken implements AuthenticationToken {

// 密钥
private String token;
/**
* The username
*/

private String username;

/**
* The password, in char[] format
*/
private char[] password;

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

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

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


}

如何生成token

结合jwt创建token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package com.xia.zhb.utils;


import com.auth0.jwt.JWT;

import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.xia.zhb.controller.common.Constants;
import com.xia.zhb.controller.common.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import java.util.Map.Entry;

@Slf4j
@Component
public class JwtUtil {
//token过期时间
public static String tokenExpireTime;

//签名密钥
public static String secret;

//token标识
public static String header;

@Value("${token.expireTime}")
public void setTokenExpireTime(String tokenExpireTime)
{
JwtUtil.tokenExpireTime=tokenExpireTime;
}

@Value("${token.secret}")
public void setSecret(String secret)
{
JwtUtil.secret=secret;
}

@Value("${token.header}")
public void setHeader(String header)
{
JwtUtil.header=header;
}
/**
*      * 校验token是否正确
*      *
*      * @param token 
*      * @param secret 密钥
*      * @return 是否正确
*     
*/
public static boolean verify(String token, String username) {

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 true;
}

/***
* @Description: 获取token
* @Param: * @param request
* @return: java.lang.String
* @Author: 虾
* @Date: 2020/6/26 5:18
*/
public static String getToken(ServletRequest request) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
String token=null;
try {
token = httpRequest.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
}catch (NullPointerException e)
{
log.info("无token");
return null;
}

return token;

}
/**
*      * 获得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) {
try {
//token过期时间
Date date = new Date(System.currentTimeMillis() + Integer.parseInt(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;
}
}

/**
* 刷新 token的过期时间
*/
public static String refreshTokenExpired(String token) {
DecodedJWT jwt = JWT.decode(token);
Map<String, Claim> claims = jwt.getClaims();
try {
Date date = new Date(System.currentTimeMillis() + Integer.parseInt(tokenExpireTime) * 60 * 1000);
Algorithm algorithm = Algorithm.HMAC256(secret);
Builder builer = JWT.create().withExpiresAt(date);
for (Entry<String, Claim> entry : claims.entrySet()) {
builer.withClaim(entry.getKey(), entry.getValue().asString());
}
return builer.sign(algorithm);
} catch (Exception e) {
return null;
}
}


}

新增处理token的realm JwtRealm

JwtRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.xia.zhb.shiro.realm;

import com.xia.zhb.shiro.JWTToken;
import com.xia.zhb.entity.SysUser;
import com.xia.zhb.service.SysUserService;
import com.xia.zhb.utils.JwtUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

public class JwtRealm extends AuthorizingRealm {

@Resource
private SysUserService sysUserService;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名
String username = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> setrole = new HashSet<String>();
setrole.add(sysUserService.queryUsernameToRole(username));
// 给该用户设置角色,角色信息存在 t_role 表中取
authorizationInfo.setRoles(setrole);
// 给该用户设置权限,权限信息存在 t_permission 表中取
Set<String> setpermission = new HashSet<String>();
setpermission.add(sysUserService.queryUsernameToPermission(username));
authorizationInfo.setStringPermissions(setpermission);
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//强转
JWTToken jwtToken=(JWTToken)authenticationToken;
//验证token有效并且获得用户信息用于验证通过
String token=(String) jwtToken.getCredentials();
String username=JwtUtil.getUsername(token);

if (JwtUtil.verify(token,username )) {
// 根据用户名从数据库中查询该用户
SysUser user = sysUserService.queryUsername(username);
if (user != null) {
// 使用用户名作为盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
// 传入用户名和密码进行身份认证,并返回认证信息
return new SimpleAuthenticationInfo(user, user.getUserName(), getName());

} else {
throw new AuthenticationException("该用户不存在!");
}
}else{
throw new AuthenticationException("非法token!");
}
}


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


}

为了让其生效 请看shiroconfig代码

处理自定义token报错 重写ModularRealmAuthenticator

MultiRealmAuthenticator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.xia.zhb.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;

import java.util.Collection;


@Slf4j
public class MultiRealmAuthenticator extends ModularRealmAuthenticator {

@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token)
throws AuthenticationException {
AuthenticationStrategy strategy = getAuthenticationStrategy();

AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
AuthenticationException authenticationException = null;
for (Realm realm : realms) {

aggregate = strategy.beforeAttempt(realm, token, aggregate);

if (realm.supports(token)) {

log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (AuthenticationException e) {
authenticationException = e;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, e);
}
}

aggregate = strategy.afterAttempt(realm, token, info, aggregate, authenticationException);

} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
if(authenticationException != null){
throw authenticationException;
}
aggregate = strategy.afterAllAttempts(token, aggregate);

return aggregate;

}
}

完成这些后不带token的login登录就进入了自定义Realm MyRealm 进行验证

而带token进入JwtRealm进行验证

参考:

https://blog.csdn.net/z237794193/article/details/91046302

https://www.jianshu.com/p/3c51832f1051

https://blog.csdn.net/pengjunlee/article/details/95600843


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!