Spring Boot Security 结合 JWT 实现无状态的分布式API接口
简介
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 详解。
建表
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);
项目结构
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
<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
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
/** * 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
@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
@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"
返回
eyjhbgcioijiuzuxmij9.eyjzdwiioijhzg1pbiisimv4cci6mtu1ndq1mzuwmx0.sglveqndgul9ph1op3lh9xrdzjis42vkbapd2npjt7e1tkhcey7aufixnzg9vc885_jtq4-h8r6yctrrjzl8fq
不带token访问资源
curl -x post -d "name=zhangsan"
返回,拒绝访问
{ "timestamp": "2019-03-31t08:50:55.894+0000", "status": 403, "error": "forbidden", "message": "access denied", "path": "/auth/login" }
携带token访问资源
curl -x post -h "authorization: eyjhbgcioijiuzuxmij9.eyjzdwiioijhzg1pbiisimv4cci6mtu1ndq1mzuwmx0.sglveqndgul9ph1op3lh9xrdzjis42vkbapd2npjt7e1tkhcey7aufixnzg9vc885_jtq4-h8r6yctrrjzl8fq" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回正确
hi zhangsan , you have 'admin' role
源码
https://github.com/gf-huanchupk/springbootlearning/tree/master/springboot-jwt
总结
以上所述是小编给大家介绍的spring boot security 结合 jwt 实现无状态的分布式api接口,希望对大家有所帮助