本文介绍了spring-boot结合shrio实现jwt的方法,分享给大家,具体如下:
关于验证大致分为两个方面:
- 用户登录时的验证;
- 用户登录后每次访问时的权限认证
主要解决方法:使用自定义的shiro filter
项目搭建:
这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google。
pom.mx引入相关jar包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!-- shiro 权限管理 -->
<dependency>
<groupid>org.apache.shiro</groupid>
<artifactid>shiro-spring</artifactid>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupid>org.apache.shiro</groupid>
<artifactid>shiro-core</artifactid>
<version>${shiro.version}</version>
</dependency>
<!-- jwt -->
<dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt</artifactid>
<version> 0.9 . 0 </version>
</dependency>
|
shrio 的相关配置
划重点!!自定义了一个filter
1
|
filtermap.put( "jwtfilter" , new jwtfilter());
|
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
|
@configuration
public class shiroconfig {
@bean
public shirofilterfactorybean getshirofilterfactorybean(securitymanager securitymanager) {
shirofilterfactorybean shirofilterfactorybean = new shirofilterfactorybean();
shirofilterfactorybean.setsecuritymanager(securitymanager);
// 添加自己的过滤器并且取名为jwtfilter
map<string, filter> filtermap = new hashmap<>();
filtermap.put( "jwtfilter" , new jwtfilter());
shirofilterfactorybean.setfilters(filtermap);
/*
* 自定义url规则
* http://shiro.apache.org/web.html#urls-
*/
map<string, string> filterchaindefinitionmap = shirofilterfactorybean.getfilterchaindefinitionmap();
filterchaindefinitionmap.put("/**", "jwtfilter");
shirofilterfactorybean.setfilterchaindefinitionmap(filterchaindefinitionmap);
return shirofilterfactorybean;
}
/**
* securitymanager 不用直接注入shirodbrealm,可能会导致事务失效
* 解决方法见 handlecontextrefresh
* http://www.debugrun.com/a/nks9ejq.html
*/
@bean("securitymanager")
public defaultwebsecuritymanager securitymanager(tokenrealm tokenrealm) {
defaultwebsecuritymanager manager = new defaultwebsecuritymanager();
manager.setrealm(tokenrealm);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#sessionmanagement-statelessapplications%28sessionless%29
*/
defaultsubjectdao subjectdao = new defaultsubjectdao();
defaultsessionstorageevaluator defaultsessionstorageevaluator = new defaultsessionstorageevaluator();
defaultsessionstorageevaluator.setsessionstorageenabled( false );
subjectdao.setsessionstorageevaluator(defaultsessionstorageevaluator);
manager.setsubjectdao(subjectdao);
return manager;
}
@bean
public lifecyclebeanpostprocessor lifecyclebeanpostprocessor() {
return new lifecyclebeanpostprocessor();
}
@bean (name = "tokenrealm" )
@dependson ( "lifecyclebeanpostprocessor" )
public tokenrealm tokenrealm() {
return new tokenrealm();
}
@bean
@dependson ( "lifecyclebeanpostprocessor" )
public defaultadvisorautoproxycreator defaultadvisorautoproxycreator() {
defaultadvisorautoproxycreator defaultadvisorautoproxycreator = new defaultadvisorautoproxycreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
// https://zhuanlan.zhihu.com/p/29161098
defaultadvisorautoproxycreator.setproxytargetclass( true );
return defaultadvisorautoproxycreator;
}
@bean
public authorizationattributesourceadvisor getauthorizationattributesourceadvisor(securitymanager securitymanager) {
authorizationattributesourceadvisor authorizationattributesourceadvisor = new authorizationattributesourceadvisor();
authorizationattributesourceadvisor.setsecuritymanager(securitymanager);
return new authorizationattributesourceadvisor();
}
}
|
自定义shrio filter
执行顺序:prehandle -> dofilterinternal -> executelogin -> onloginsuccess
主要判断是不是登录请求的是 dofilterinternal
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 jwtfilter extends basichttpauthenticationfilter {
/**
* 自定义执行登录的方法
*/
@override
protected boolean executelogin(servletrequest request, servletresponse response) throws ioexception {
httpservletrequest httpservletrequest = (httpservletrequest) request;
usernamepasswordtoken usernamepasswordtoken = json.parseobject(httpservletrequest.getinputstream(), usernamepasswordtoken. class );
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
subject subject = this .getsubject(request, response);
subject.login(usernamepasswordtoken);
return this .onloginsuccess(usernamepasswordtoken, subject, request, response);
//错误抛出异常
}
/**
* 最先执行的方法
*/
@override
protected boolean prehandle(servletrequest request, servletresponse response) throws exception {
return super .prehandle(request, response);
}
/**
* 登录成功后登录的操作
* 加上jwt 的header
*/
@override
protected boolean onloginsuccess(authenticationtoken token, subject subject, servletrequest request, servletresponse response) {
httpservletresponse httpservletresponse = (httpservletresponse) response;
string jwttoken = jwts.builder()
.setid(token.getprincipal().tostring())
.setexpiration(datetime.now().plusminutes( 30 ).todate())
.signwith(signaturealgorithm.hs256, jwtcost.signaturekey)
.compact();
httpservletresponse.addheader(authorization_header, jwttoken);
return true ;
}
/**
* 登录以及校验的主要流程
* 判断是否是登录,或者是登陆后普通的一次请求
*/
@override
public void dofilterinternal(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception {
httpservletrequest httpservletrequest = (httpservletrequest) servletrequest;
httpservletresponse httpservletresponse = (httpservletresponse) servletresponse;
string servletpath = httpservletrequest.getservletpath();
if (stringutils.equals(servletpath, "/login" )) {
//执行登录
this .executelogin(servletrequest, servletresponse);
} else {
string authenticationheader = httpservletrequest.getheader(authorization_header);
if (stringutils.isnotempty(authenticationheader)) {
claims body = jwts.parser()
.setsigningkey(jwtcost.signaturekey)
.parseclaimsjws(authenticationheader)
.getbody();
if (body != null ) {
//更新token
body.setexpiration(datetime.now().plusminutes( 30 ).todate());
string updatetoken = jwts.builder().setclaims(body).compact();
httpservletresponse.addheader(authorization_header, updatetoken);
//添加用户凭证
principalcollection principals = new simpleprincipalcollection(body.getid(), jwtcost.usernamepasswordrealm); //拼装shiro用户信息
websubject.builder builder = new websubject.builder(servletrequest, servletresponse);
builder.principals(principals);
builder.authenticated( true );
builder.sessioncreationenabled( false );
websubject subject = builder.buildwebsubject();
//塞入容器,统一调用
threadcontext.bind(subject);
filterchain.dofilter(httpservletrequest, httpservletresponse);
}
} else {
httpservletresponse.setstatus(httpstatus.forbidden.value());
}
}
}
}
|
登录失败处理
处理shrio异常
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
|
@restcontrolleradvice
public class globalcontrollerexceptionhandler {
@exceptionhandler (value = exception. class )
public object allexceptionhandler(httpservletrequest request, httpservletresponse response, exception exception) {
string message = exception.getcause().getmessage();
logutil.error(message);
return new resultinfo(exception.getclass().getname(), message);
}
/*=========== shiro 异常拦截==============*/
@exceptionhandler (value = incorrectcredentialsexception. class )
public string incorrectcredentialsexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "incorrectcredentialsexception" ;
}
@exceptionhandler (value = unknownaccountexception. class )
public string unknownaccountexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "unknownaccountexception" ;
}
@exceptionhandler (value = lockedaccountexception. class )
public string lockedaccountexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "lockedaccountexception" ;
}
@exceptionhandler (value = excessiveattemptsexception. class )
public string excessiveattemptsexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "excessiveattemptsexception" ;
}
@exceptionhandler (value = authenticationexception. class )
public string authenticationexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "authenticationexception" ;
}
@exceptionhandler (value = unauthorizedexception. class )
public string unauthorizedexception(httpservletrequest request, httpservletresponse response, exception exception) {
response.setstatus(httpstatus.forbidden.value());
return "unauthorizedexception" ;
}
}
|
处理jwt异常
这是个坑,因为是在filter内发生的异常,@exceptionhandler是截获不到的。
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
|
/**
* 截获spring boot error页面
*/
@restcontroller
public class globalexceptionhandler implements errorcontroller {
@override
public string geterrorpath() {
return "/error" ;
}
@requestmapping (value = "/error" )
public object error(httpservletrequest request, httpservletresponse response) throws exception {
// 错误处理逻辑
exception exception = (exception) request.getattribute( "javax.servlet.error.exception" );
throwable cause = exception.getcause();
if (cause instanceof expiredjwtexception) {
response.setstatus(httpstatus.gateway_timeout.value());
return new resultinfo( "expiredjwtexception" , cause.getmessage());
}
if (cause instanceof malformedjwtexception) {
response.setstatus(httpstatus.forbidden.value());
return new resultinfo( "malformedjwtexception" , cause.getmessage());
}
return new resultinfo(cause.getcause().getmessage(), cause.getmessage());
}
}
|
关于权限等授权信息,可以直接放到redis中实现缓存。我认为也是不错的。
源码奉上:githup-shiro分支 :温馨提示:平时测试代码可能比较乱。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://segmentfault.com/a/1190000014750168