JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。JSON Web Token 入門教程 - 阮一峯,這篇文章能夠幫你瞭解JWT的概念。本文重點講解Spring Boot 結合 jwt ,來實現先後端分離中,接口的安全調用。html
以前的文章已經對 Spring Security 進行了講解,這一節對涉及到 Spring Security 的配置不詳細講解。若不瞭解 Spring Security 先移步到 Spring Boot Security 詳解。java
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
<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>
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
@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(); } }
/** * 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; } }
@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); } }
@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; } }
關鍵代碼就是這些,其餘類代碼參照後面提供的源碼地址。mysql
登陸,獲取tokengit
curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login
返回程序員
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ
不帶token訪問資源github
curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回,拒絕訪問web
{ "timestamp": "2019-03-31T08:50:55.894+0000", "status": 403, "error": "Forbidden", "message": "Access Denied", "path": "/auth/login" }
攜帶token訪問資源spring
curl -X POST -H "Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回正確sql
hi zhangsan , you have 'admin' role
https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-jwtdocker
Spring Boot 自定義 starter
Spring Boot 整合 mybatis-plus
Spring Boot 整合 spring cache
Spring Boot 整合 rabbitmq
Spring Boot 整合 elasticsearch
Spring Boot 整合 docker
Spring Boot 整合 elk
Spring Boot Admin 2.0 詳解
Spring Boot 整合 apollo
Spring Boot Security 詳解
Spring Boot Security 整合 OAuth2 設計安全API接口服務
Spring Boot Security 整合 JWT 實現 無狀態的分佈式API接口
Spring Cloud Gateway 入門
Spring Cloud Gateway 之 Predict
Spring Cloud Gateway 之 Filter
Spring Cloud Gateway 之 限流
Spring Cloud Gateway 之 服務註冊與發現
Dubbo 搭建管理控制檯
Dubbo 建立 提供者 消費者
Dubbo 配置
Dubbo 高可用
服務 Docker 化
Docker 私有倉庫搭建
DockerSwarm 集羣環境搭建
DockerSwarm 微服務部署
歡迎掃碼或微信搜索公衆號《程序員果果》關注我,關注有驚喜~