簡介
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接口,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!