简介
json web token(缩写 jwt)是目前最流行的跨域认证解决方案。json web token 入门教程 这篇文章可以帮你了解jwt的概念。本文重点讲解spring boot 结合 jwt ,来实现前后端分离中,接口的安全调用。
spring security,这是一种基于 spring aop 和 servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 web 请求级和方法调用级处理身份确认和授权。
快速上手
之前的文章已经对 spring security 进行了讲解,这一节对涉及到 spring security 的配置不详细讲解。若不了解 spring security 先移步到 spring boot security 详解。
建表
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
|
drop table if exists `user`; drop table if exists `role`; drop table if exists `user_role`; drop table if exists `role_permission`; drop table if exists `permission`; create table `user` ( `id` bigint( 11 ) not null auto_increment, `username` varchar( 255 ) not null , `password` varchar( 255 ) not null , primary key (`id`) ); create table `role` ( `id` bigint( 11 ) not null auto_increment, `name` varchar( 255 ) not null , primary key (`id`) ); create table `user_role` ( `user_id` bigint( 11 ) not null , `role_id` bigint( 11 ) not null ); create table `role_permission` ( `role_id` bigint( 11 ) not null , `permission_id` bigint( 11 ) not null ); create table `permission` ( `id` bigint( 11 ) not null auto_increment, `url` varchar( 255 ) not null , `name` varchar( 255 ) not null , `description` varchar( 255 ) null , `pid` bigint( 11 ) not null , primary key (`id`) ); insert into user (id, username, password) values ( 1 , 'user' , 'e10adc3949ba59abbe56e057f20f883e' ); insert into user (id, username , password) values ( 2 , 'admin' , 'e10adc3949ba59abbe56e057f20f883e' ); insert into role (id, name) values ( 1 , 'user' ); insert into role (id, name) values ( 2 , 'admin' ); insert into permission (id, url, name, pid) values ( 1 , '/user/hi' , '' , 0 ); insert into permission (id, url, name, pid) values ( 2 , '/admin/hi' , '' , 0 ); insert into user_role (user_id, role_id) values ( 1 , 1 ); insert into user_role (user_id, role_id) values ( 2 , 1 ); insert into user_role (user_id, role_id) values ( 2 , 2 ); insert into role_permission (role_id, permission_id) values ( 1 , 1 ); insert into role_permission (role_id, permission_id) values ( 2 , 1 ); insert into role_permission (role_id, permission_id) values ( 2 , 2 ); |
项目结构
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
|
resources |___application.yml java |___com | |____gf | | |____springbootjwtapplication.java | | |____config | | | |____.ds_store | | | |____securityconfig.java | | | |____myfiltersecurityinterceptor.java | | | |____myinvocationsecuritymetadatasourceservice.java | | | |____myaccessdecisionmanager.java | | |____entity | | | |____user.java | | | |____rolepermisson.java | | | |____role.java | | |____mapper | | | |____permissionmapper.java | | | |____usermapper.java | | | |____rolemapper.java | | |____utils | | | |____jwttokenutil.java | | |____controller | | | |____authcontroller.java | | |____filter | | | |____jwttokenfilter.java | | |____service | | | |____impl | | | | |____authserviceimpl.java | | | | |____userdetailsserviceimpl.java | | | |____authservice.java |
关键代码
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>io.jsonwebtoken</groupid> <artifactid>jjwt</artifactid> <version> 0.9 . 0 </version> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <scope>runtime</scope> </dependency> <dependency> <groupid>org.mybatis.spring.boot</groupid> <artifactid>mybatis-spring-boot-starter</artifactid> <version> 2.0 . 0 </version> </dependency> |
application.yml
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
|
spring: datasource: driver- class -name: com.mysql.cj.jdbc.driver url: jdbc:mysql: //localhost:3306/spring-security-jwt?useunicode=true&characterencoding=utf-8&usessl=false username: root password: root securityconfig @configuration @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { @autowired private userdetailsservice userdetailsservice; @autowired public void configureglobal(authenticationmanagerbuilder auth) throws exception { //校验用户 auth.userdetailsservice( userdetailsservice ).passwordencoder( new passwordencoder() { //对密码进行加密 @override public string encode(charsequence charsequence) { system.out.println(charsequence.tostring()); return digestutils.md5digestashex(charsequence.tostring().getbytes()); } //对密码进行判断匹配 @override public boolean matches(charsequence charsequence, string s) { string encode = digestutils.md5digestashex(charsequence.tostring().getbytes()); boolean res = s.equals( encode ); return res; } } ); } @override protected void configure(httpsecurity http) throws exception { http.csrf().disable() //因为使用jwt,所以不需要httpsession .sessionmanagement().sessioncreationpolicy( sessioncreationpolicy.stateless).and() .authorizerequests() //options请求全部放行 .antmatchers( httpmethod.options, "/**" ).permitall() //登录接口放行 .antmatchers( "/auth/login" ).permitall() //其他接口全部接受验证 .anyrequest().authenticated(); //使用自定义的 token过滤器 验证请求的token是否合法 http.addfilterbefore(authenticationtokenfilterbean(), usernamepasswordauthenticationfilter. class ); http.headers().cachecontrol(); } @bean public jwttokenfilter authenticationtokenfilterbean() throws exception { return new jwttokenfilter(); } @bean @override public authenticationmanager authenticationmanagerbean() throws exception { return super .authenticationmanagerbean(); } } |
jwttokenutil
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
|
/** * jwt 工具类 */ @component public class jwttokenutil implements serializable { private static final string claim_key_username = "sub" ; /** * 5天(毫秒) */ private static final long expiration_time = 432000000 ; /** * jwt密码 */ private static final string secret = "secret" ; /** * 签发jwt */ public string generatetoken(userdetails userdetails) { map<string, object> claims = new hashmap<>( 16 ); claims.put( claim_key_username, userdetails.getusername() ); return jwts.builder() .setclaims( claims ) .setexpiration( new date( instant.now().toepochmilli() + expiration_time ) ) .signwith( signaturealgorithm.hs512, secret ) .compact(); } /** * 验证jwt */ public boolean validatetoken(string token, userdetails userdetails) { user user = (user) userdetails; string username = getusernamefromtoken( token ); return (username.equals( user.getusername() ) && !istokenexpired( token )); } /** * 获取token是否过期 */ public boolean istokenexpired(string token) { date expiration = getexpirationdatefromtoken( token ); return expiration.before( new date() ); } /** * 根据token获取username */ public string getusernamefromtoken(string token) { string username = getclaimsfromtoken( token ).getsubject(); return username; } /** * 获取token的过期时间 */ public date getexpirationdatefromtoken(string token) { date expiration = getclaimsfromtoken( token ).getexpiration(); return expiration; } /** * 解析jwt */ private claims getclaimsfromtoken(string token) { claims claims = jwts.parser() .setsigningkey( secret ) .parseclaimsjws( token ) .getbody(); return claims; } } |
jwttokenfilter
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
|
@component public class jwttokenfilter extends onceperrequestfilter { @autowired private userdetailsservice userdetailsservice; @autowired private jwttokenutil jwttokenutil; /** * 存放token的header key */ public static final string header_string = "authorization" ; @override protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain chain) throws servletexception, ioexception { string token = request.getheader( header_string ); if ( null != token) { string username = jwttokenutil.getusernamefromtoken(token); if (username != null && securitycontextholder.getcontext().getauthentication() == null ) { userdetails userdetails = this .userdetailsservice.loaduserbyusername(username); if (jwttokenutil.validatetoken(token, userdetails)) { usernamepasswordauthenticationtoken authentication = new usernamepasswordauthenticationtoken( userdetails, null , userdetails.getauthorities()); authentication.setdetails( new webauthenticationdetailssource().builddetails( request)); securitycontextholder.getcontext().setauthentication(authentication); } } } chain.dofilter(request, response); } } |
authserviceimpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@service public class authserviceimpl implements authservice { @autowired private authenticationmanager authenticationmanager; @autowired private userdetailsservice userdetailsservice; @autowired private jwttokenutil jwttokenutil; @override public string login(string username, string password) { usernamepasswordauthenticationtoken uptoken = new usernamepasswordauthenticationtoken( username, password ); authentication authentication = authenticationmanager.authenticate(uptoken); securitycontextholder.getcontext().setauthentication(authentication); userdetails userdetails = userdetailsservice.loaduserbyusername( username ); string token = jwttokenutil.generatetoken(userdetails); return token; } } |
关键代码就是这些,其他类代码参照后面提供的源码地址。
验证
登录,获取token
curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login返回
eyjhbgcioijiuzuxmij9.eyjzdwiioijhzg1pbiisimv4cci6mtu1ndq1mzuwmx0.sglveqndgul9ph1op3lh9xrdzjis42vkbapd2npjt7e1tkhcey7aufixnzg9vc885_jtq4-h8r6yctrrjzl8fq
不带token访问资源
curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回,拒绝访问
1
2
3
4
5
6
7
|
{ "timestamp" : "2019-03-31t08:50:55.894+0000" , "status" : 403 , "error" : "forbidden" , "message" : "access denied" , "path" : "/auth/login" } |
携带token访问资源
1
|
curl -x post -h "authorization: eyjhbgcioijiuzuxmij9.eyjzdwiioijhzg1pbiisimv4cci6mtu1ndq1mzuwmx0.sglveqndgul9ph1op3lh9xrdzjis42vkbapd2npjt7e1tkhcey7aufixnzg9vc885_jtq4-h8r6yctrrjzl8fq" -d "name=zhangsan" http: //127.0.0.1:8080/admin/hi |
返回正确
1
|
hi zhangsan , you have 'admin' role |
源码
https://github.com/gf-huanchupk/springbootlearning/tree/master/springboot-jwt
总结
以上所述是小编给大家介绍的spring boot security 结合 jwt 实现无状态的分布式api接口,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!