在之前的一篇文章 中我们已经详细介绍了什么是JWT以及JWT的组成和工作流程。本文将介绍JWT在Spring Boot项目中该如何应用。
一、导入JAR 首先我们使用Maven进行导入,我导入的版本是0.9.0,若需导入其它版本的,请访问mvnrepository 获取更多版本的信息
1 2 3 4 5 6 <dependency >     <groupId > io.jsonwebtoken</groupId >      <artifactId > jjwt</artifactId >      <version > 0.9.0</version >  </dependency > 
 
二、编写工具类 该工具类将JWT的常用功能进行封装,方便开发人员使用
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 package  cn.frankfang.utils;  import  io.jsonwebtoken.Claims;import  io.jsonwebtoken.Jwts;import  io.jsonwebtoken.SignatureAlgorithm;  import  java.util.Date;import  java.util.HashMap;import  java.util.List;  public  class  JwtTokenUtils   {      public  static  final  String TOKEN_HEADER = "Authorization" ;     public  static  final  String TOKEN_PREFIX = "Bearer " ;            private  static  final  String PRIMARY_KEY = "MyJwtSecret" ;          private  static  final  String ISS = "FrankFang" ;          private  static  final  String ROLE_CLAIMS = "role" ;          private  static  final  long  EXPIRATION = 3600L ;                        public  static  String createToken (String username, List<String> roles)   {                  HashMap<String, Object> map = new  HashMap<>();         map.put(ROLE_CLAIMS, roles);         return  Jwts.builder()                                  .signWith(SignatureAlgorithm.HS512, PRIMARY_KEY)                                  .setClaims(map)                                  .setIssuer(ISS)                 .setSubject(username)                 .setIssuedAt(new  Date())                 .setExpiration(new  Date(System.currentTimeMillis() + EXPIRATION * 24  * 1000 ))                 .compact();     }            public  static  String getUsername (String token)  {         return  getTokenBody(token).getSubject();     }        	@SuppressWarnings("unchecked")  	public  static  List<String> getUserRole (String token)  {         return  (List<String>) getTokenBody(token).get(ROLE_CLAIMS);     }            public  static  boolean  isExpiration (String token)  {         return  getTokenBody(token).getExpiration().before(new  Date());     }            private  static  Claims getTokenBody (String token)  {         return  Jwts.parser()                 .setSigningKey(PRIMARY_KEY)                 .parseClaimsJws(token)                 .getBody();     } } 
 
三、自定义过滤器 由于本项目采用了Spring Security作为安全管理框架,因此需要继承Spring Security的UsernamePasswordAuthenticationFilter类以及BasicAuthenticationFilter类并在Spring Security的配置类中添加这两个自定义的过滤器
1、自定义登录过滤器 编写一个CustomLoginFilter类继承UsernamePasswordAuthenticationFilter类
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 package  cn.frankfang.filter;import  java.io.PrintWriter;import  java.util.ArrayList;import  java.util.Collection;import  java.util.HashMap;import  java.util.List;import  java.util.Map;import  javax.servlet.FilterChain;import  javax.servlet.http.HttpServletRequest;import  javax.servlet.http.HttpServletResponse;import  org.springframework.security.authentication.AuthenticationManager;import  org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import  org.springframework.security.core.Authentication;import  org.springframework.security.core.GrantedAuthority;import  org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import  com.fasterxml.jackson.databind.ObjectMapper;import  cn.frankfang.entity.User;import  cn.frankfang.bean.JsonResponse;import  cn.frankfang.utils.JwtTokenUtils;public  class  CustomLoginFilter  extends  UsernamePasswordAuthenticationFilter   {	private  AuthenticationManager authenticationManager; 	public  CustomLoginFilter (AuthenticationManager authenticationManager)   { 		this .authenticationManager = authenticationManager;          		super .setFilterProcessesUrl("/api/login" ); 	} 	 	@Override  	public  Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response)   { 		try  { 			User user = new  ObjectMapper().readValue(request.getInputStream(), User.class); 			return  authenticationManager.authenticate( 					new  UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new  ArrayList<>())); 		} catch  (Exception e) { 			e.printStackTrace(); 			try  { 				 				response.setContentType("application/json;charset=utf-8" ); 				PrintWriter out = response.getWriter(); 				JsonResponse result = new  JsonResponse(HttpServletResponse.SC_UNAUTHORIZED, "账号或密码错误!" ); 				out.write(new  ObjectMapper().writeValueAsString(result)); 				out.flush(); 				out.close(); 			} catch  (Exception e1) { 				e1.printStackTrace(); 			} 			throw  new  RuntimeException(); 		} 		 	} 	 	@Override  	protected  void  successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain,  			Authentication auth)   {		try  { 			 			User user = (User) auth.getPrincipal(); 			 			Collection<? extends GrantedAuthority> authorities = user.getAuthorities(); 			List<String> roles = new  ArrayList<>(); 			for  (GrantedAuthority authority : authorities) { 				roles.add(authority.getAuthority()); 			} 			 			String token = JwtTokenUtils.createToken(user.getUsername(), roles); 			try  { 				 				response.setContentType("application/json;charset=utf-8" ); 				PrintWriter out = response.getWriter(); 				Map<String, Object> data = new  HashMap<>(); 				data.put("id" , user.getId()); 				data.put("username" , user.getUsername()); 				data.put("token" , "Bearer "  + token); 				JsonResponse result = new  JsonResponse(HttpServletResponse.SC_OK, "登录成功!" , data); 				out.write(new  ObjectMapper().writeValueAsString(result)); 				out.flush(); 				out.close(); 			} catch  (Exception e1) { 				e1.printStackTrace(); 			} 		} catch  (Exception e) { 			e.printStackTrace(); 		} 	} } 
 
用户实体类User部分内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 public  class  User  implements  UserDetails , Serializable   {	 	private  Integer id; 	 	private  String username; 	 	private  String password; 	 	private  List<Role> roles; 	 	 } 
 
用户角色类Role部分内容如下:
1 2 3 4 5 6 7 8 public  class  Role  implements  Serializable   {	 	private  Integer id; 	 	private  String name; 	 	 } 
 
自定义JSON格式类JsonResponse部分内容如下:
1 2 3 4 5 6 7 8 9 10 public  class  JsonResponse   {	 	private  Integer status; 	 	private  String message; 	 	private  Object data; 	 	 } 
 
2、自定义授权过滤器 编写CustomAuthenticationFilter类继承BasicAuthenticationFilter类
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 83 84 85 86 87 package  cn.frankfang.filter;import  java.io.PrintWriter;import  java.util.Collection;import  java.util.HashMap;import  java.util.HashSet;import  java.util.List;import  java.util.Map;import  javax.servlet.FilterChain;import  javax.servlet.http.HttpServletRequest;import  javax.servlet.http.HttpServletResponse;import  org.springframework.security.authentication.AuthenticationManager;import  org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import  org.springframework.security.core.GrantedAuthority;import  org.springframework.security.core.authority.SimpleGrantedAuthority;import  org.springframework.security.core.context.SecurityContextHolder;import  org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import  com.fasterxml.jackson.databind.ObjectMapper;import  cn.frankfang.utils.JwtTokenUtils;public  class  CustomAuthenticationFilter  extends  BasicAuthenticationFilter   {	 	public  CustomAuthenticationFilter (AuthenticationManager authenticationManager)   { 		super (authenticationManager); 	} 	 	@Override  	protected  void  doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain)   { 		try  { 			 			String header = request.getHeader("Authorization" ); 			 			if  (header == null  || !header.startsWith("Bearer " )) { 				chain.doFilter(request, response); 				 				return ; 			} 			 			UsernamePasswordAuthenticationToken authentication = getAuthentication(header); 			 			SecurityContextHolder.getContext().setAuthentication(authentication); 			chain.doFilter(request, response); 		} catch  (Exception e) { 			responseJson(response); 			e.printStackTrace(); 		} 	} 	 	private  void  responseJson (HttpServletResponse response)   { 		try  { 			 			response.setContentType("application/json;charset=utf-8" ); 			response.setStatus(HttpServletResponse.SC_FORBIDDEN); 			PrintWriter out = response.getWriter(); 			Map<String,Object> map = new  HashMap<>(); 			map.put("status" ,HttpServletResponse.SC_FORBIDDEN); 			map.put("message" , "请登录" ); 			out.write(new  ObjectMapper().writeValueAsString(map)); 			out.flush(); 			out.close(); 		} catch  (Exception e1) { 			e1.printStackTrace(); 		} 	} 	 	private  UsernamePasswordAuthenticationToken getAuthentication (String header)   {          		String token = header.replace(JwtTokenUtils.TOKEN_PREFIX, "" );          		String username = JwtTokenUtils.getUsername(token);          		List<String> roles = JwtTokenUtils.getUserRole(token); 		Collection<GrantedAuthority> authorities = new  HashSet<>();         if  (roles!=null ) {             for  (String role : roles) {                 authorities.add(new  SimpleGrantedAuthority(role));             }         }         if  (username != null ){             return  new  UsernamePasswordAuthenticationToken(username, null , authorities);         }         return  null ; 	} } 
 
四、添加过滤器 在Spring 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package  cn.frankfang.config;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import  org.springframework.security.config.annotation.web.builders.HttpSecurity;import  org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import  org.springframework.security.config.http.SessionCreationPolicy;import  org.springframework.security.core.userdetails.UserDetailsService;import  org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import  org.springframework.security.crypto.password.PasswordEncoder;import  cn.frankfang.filter.CustomAuthenticationFilter;import  cn.frankfang.filter.CustomLoginFilter;@Configuration public  class  WebSecurityConfig  extends  WebSecurityConfigurerAdapter   {	 	@Autowired  	private  UserDetailsService service; 	 	@Bean   	PasswordEncoder passwordEncoder ()   {  		return  new  BCryptPasswordEncoder(); 	} 	 	 	@Override      protected  void  configure (AuthenticationManagerBuilder auth)  throws  Exception  { 		         auth.userDetailsService(service);     }      	@Override      protected  void  configure (HttpSecurity http)  throws  Exception  { 		 		http.authorizeRequests() 			.antMatchers("/api/admin/**" ).hasRole("admin" ) 			.anyRequest().permitAll(); 		 		 		http.cors(); 		 		 		http.csrf().disable(); 		 		         http.sessionManagement()         	.sessionCreationPolicy(SessionCreationPolicy.STATELESS)         	.and()         	.authorizeRequests();                           http.addFilter(new  CustomLoginFilter(authenticationManager()));                           http.addFilter(new  CustomAuthenticationFilter(authenticationManager())); 	} } 
 
五、测试 在整合完成之后,我们便可以启动项目进行测试,这里我们使用Postman 模拟前端进行登录:
登录成功后JWT生成的Token:
1 Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBRE1JTiIsInJvbGUiOlsiUk9MRV9hZG1pbiJdLCJpc3MiOiJGcmFua0ZhbmciLCJleHAiOjE2MDc3NDgzMTEsImlhdCI6MTYwNzY2MTkxMX0.GS2KZ5LIJeCsLL88idGHSo68THzRv6TwMa0knNITqSquV-iY8-wrnyezIKNy7zKAsuy9NFVzSpUWOuqOZwxL9A 
 
之后在发起需要进行权限验证的请求时需要在请求头中添加Authorization请求头,值为JWT生成的Token
当可以正常获取服务端响应的数据时便代表整合成功。