JWT介绍
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点的单点登录(SSO
)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息, 以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
优点:
体积小、传输快
支持跨域授权,因为跨域无法共享cookie
分布式系统中,很好地解决了单点登录问题
缺点:
因为JWT是无状态的,因此服务端无法控制已经生成的Token失效,是不可控的
使用场景
- 认证,这是比较常见的使用场景,只要用户登录过一次系统,之后的请求都会包含签名出来的token,通过token也可以用来实现单点登录。
- 交换信息,通过使用密钥对来安全的传送信息,可以知道发送者是谁、放置消息被篡改。
认证过程
一般是在请求头里加入Authorization,并加上Bearer标注
Springboot集成JWT
项目克隆
项目名称 springboot-jwt
项目地址: https://gitee.com/minili/springboot-demo.git
如果觉得该项目对你有帮助或者有疑问的话, 欢迎加星, 评论
添加表
一个是管理员表, 一个是存放token表
在项目下的db文件夹
1 | SET FOREIGN_KEY_CHECKS=0; |
POM.XML
主要是添加shiro, jwt, jdbc
1 | <?xml version="1.0" encoding="UTF-8"?> |
修改application.yml文件
1 | # server |
shiro JWT 配置
主体有5个文件需要添加,分别是shiroConfig、OAuth2Filer配置、OAuth2Realm、OAuth2Token、TokenGenerator
ShiroConfig配置
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/**
* Shiro配置
*/
public class ShiroConfig {
"sessionManager") (
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
"securityManager") (
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
"shiroFilter") (
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
filterMap.put("/login", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
"lifecycleBeanPostProcessor") (
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}OAuth2Filer配置
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/**
* oauth2过滤器
*/
public class OAuth2Filter extends AuthenticatingFilter {
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtil.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = getRequestToken((HttpServletRequest) request);
if(StringUtil.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
JSONObject json = new JSONObject();
json.put("code", "401");
json.put("msg", "invalid token");
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
JSONObject json = new JSONObject();
json.put("code", "401");
json.put("msg", throwable.getMessage());
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtil.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}OAuth2Realm配置
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/**
* 认证
*/
public class OAuth2Realm extends AuthorizingRealm {
private ManagerService managerService;
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
/**
* 授权(验证权限时调用, 控制role 和 permissins时使用)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ManagerInfo manager = (ManagerInfo)principals.getPrimaryPrincipal();
Integer managerId = manager.getManagerId();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 模拟权限和角色
Set<String> permsSet = new HashSet<>();
Set<String> roles = new HashSet<>();
if (managerId == 1) {
// 超级管理员-权限
permsSet.add("delete");
permsSet.add("update");
permsSet.add("view");
roles.add("admin");
} else {
// 普通管理员-权限
permsSet.add("view");
roles.add("test");
}
info.setStringPermissions(permsSet);
info.setRoles(roles);
return info;
}
/**
* 认证(登录时调用)
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
ManagerToken managerToken = managerService.queryByToken(accessToken);
//token失效
SimpleDateFormat sm = new SimpleDateFormat("yyyyMMddHHmmss");
Date expireTime;
boolean flag = true;
try {
expireTime = sm.parse(managerToken.getExpireTime());
flag = managerToken == null || expireTime.getTime() < System.currentTimeMillis();
} catch (ParseException e) {
e.printStackTrace();
}
if(flag){
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//查询用户信息
ManagerInfo managerInfo = managerService.getManagerInfo(managerToken.managerId);
//账号锁定
// if(managerInfo.getStatus() == 0){
// throw new LockedAccountException("账号已被锁定,请联系管理员");
// }
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(managerInfo, accessToken, getName());
return info;
}
}OAuth2Token设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* token
*/
public class OAuth2Token implements AuthenticationToken {
private String token;
public OAuth2Token(String token){
this.token = token;
}
public String getPrincipal() {
return token;
}
public Object getCredentials() {
return token;
}
}
5.生成Token
1 | /** |
Controller
1 |
|
Service
1 |
|
下面还有一些基础的类或工具, 在这里被省略(将项目克隆下来将更有利于理解)
测试
登陆
- 首先登陆获取到token(localhost:8087/login?username=cscscs&password=4297f44b13955235245b2497399d7a93)
这里测试的用户有两个一个admin(超级管理员), 一个是cscscs
测试需要认证的接口
- 不带token访问 (localhost:8087/article), 返回401, 说明现在认证有效
接下来带token访问, 收到
测试不需要认证的接口
- 在shiro的url过滤规则中, 设置了/app/**不需要认证, 不带token访问localhost:8087/app/article, 收到
测试角色认证的接口
/require_role, 这个接口需要admin角色才能访问,在OAuth2Realm中我设置了admin用户为超级管理员角色, cscscs用户为test角色
用cscscs登陆的token, 访问localhost:8087/require_role, 返回401
用admin登陆的token, 访问localhost:8087/require_role, 返回正确的json
测试权限认证的接口
localhost:8087/require_permission(该接口设置的update权限才能访问)
- cscscs被赋予了view的权限, 用他的token访问需要update权限的接口, 返回
- 用admin的token访问, 返回
结语
首先大功告成, hah
有很多不足可以改进, 如缓存啊, 更准确的权限设置啊, 但他可以帮你构建一个完整可用的JWT,
之前看了网上的例子, 理论很好, 例子却没跑通, aaaaa,,,谢谢人人开源啦
走过慢慢长夜, 追不及你的长发, 拜拜妹子也走了,拜拜