在之前的一篇文章 中我们已经详细介绍了什么是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
当可以正常获取服务端响应的数据时便代表整合成功。