【Spring Boot】IDEA + Maven + Spring Boot + JPA + Spring Security + JWT 我就是我 2022-05-26 10:49 143阅读 0赞 在上篇博客中,我们搭建好了一个用户服务框架,本篇博客紧接着用户的业务场景的使用,在此基础上集成spring security 和 jwt 实现用户的登录,注册以及权限控制。 进行框架整合之前,我们先简单了解一下Spring Security和JWT。 Spring Security : Sping Security 是能够为J2EE项目提供综合性的安全访问控制解决方案的安全框架。它依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 JWT(Json Web Token): JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。JWT作为一个无状态的授权校验技术,非常适合于分布式系统架构,因为服务端不需要保存用户状态,因此就无需采用redis等技术,在各个服务节点之间共享session数据。 下面记录下整个集成过程: 一. 引入相关依赖: <!-- Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>1.5.9.RELEASE</version> </dependency> <!-- Json Web Token --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> 二. Repository层定义根据用户名查询用户接口 /** * 根据用户名查找用户 * @param username * @return */ User findByUsername(String username); 三. 添加安全用户实体JwtUser 实现 Spring Security 的 UserDetails 接口 public class JwtUser implements UserDetails { private String username; private String password; private Collection<? extends GrantedAuthority> authorities; public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) { this.username = username; this.password = password; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @JsonIgnore @Override public String getPassword() { return password; } @JsonIgnore @Override public String getUsername() { return username; } @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } @JsonIgnore @Override public boolean isEnabled() { return true; } } 四. 添加JwtToken 工具类,包含生成Token,验证Token等方法。 @Component public class JwtTokenUtil implements Serializable{ /** * 密钥 */ private final String secret = "uqiauto"; /** * 从数据声明生成令牌 * * @param claims 数据声明 * @return 令牌 */ private String generateToken(Map<String, Object> claims) { Date expirationDate = new Date(System.currentTimeMillis() + 604800L * 1000); return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact(); } /** * 从令牌中获取数据声明 * * @param token 令牌 * @return 数据声明 */ private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } /** * 生成令牌 * * @param userDetails 用户 * @return 令牌 */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); } /** * 从令牌中获取用户名 * * @param token 令牌 * @return 用户名 */ public String getUsernameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 判断令牌是否过期 * * @param token 令牌 * @return 是否过期 */ public Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; } } /** * 刷新令牌 * * @param token 原令牌 * @return 新令牌 */ public String refreshToken(String token) { String refreshedToken; try { Claims claims = getClaimsFromToken(token); claims.put("created", new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; } /** * 验证令牌 * * @param token 令牌 * @param userDetails 用户 * @return 是否有效 */ public Boolean validateToken(String token, UserDetails userDetails) { JwtUser user = (JwtUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); } } 五. 添加用户验证方法类JwtUserDetailsServiceImpl,判断用户是否存在。 @Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user =userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException("用户不存在"); }else{ // 用户存在,给用户授权 Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new JwtUser(user.getUsername(), user.getPassword(), authorities); } } } 六. Token过滤器实现,根据请求头所带有的token,验证用户的token是否正确,是否过期等。 @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private UserDetailsService userDetailsService; private JwtTokenUtil jwtTokenUtil; @Autowired public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) { this.userDetailsService = userDetailsService; this.jwtTokenUtil = jwtTokenUtil; } public JwtAuthenticationTokenFilter() { } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String authHeader = httpServletRequest.getHeader("Authorization"); String tokenHead = "Bearer "; if (authHeader != null && authHeader.startsWith(tokenHead)) { final String authToken = authHeader.substring(tokenHead.length()); String username = jwtTokenUtil.getUsernameFromToken(authToken); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(httpServletRequest, httpServletResponse); } } 七. 添加安全配置类,除了用户身份验证地址外,其他请求均需要进行token验证,密码加密方式为BCrypt. @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService; private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; private PasswordEncoder passwordEncoder; @Autowired public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter) { this.userDetailsService = userDetailsService; this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter; this.passwordEncoder = new BCryptPasswordEncoder(); } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() // 所有/uplus/user/ 的所有请求 都放行 .antMatchers("/uplus/user/**").permitAll() // 所有请求都需要认证 .anyRequest().authenticated(); httpSecurity.headers().cacheControl(); httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } 八. IUserService中定义用户操作接口,UserServiceImpl中实现 public interface IUserService{ /** * 用户登录 * * @param username 用户名 * @param password 密码 * @return 操作结果 */ String login(String username, String password) throws Exception; /** * 用户注册 * * @param user 用户信息 * @return 操作结果 */ String register(User user); } @Service @Transactional public class UserServiceImpl implements IUserService { @Autowired private AuthenticationManager authenticationManager; private JwtUserDetailsServiceImpl jwtUserDetailsService; private JwtTokenUtil jwtTokenUtil; private UserRepository userRepository; @Autowired public UserServiceImpl( JwtUserDetailsServiceImpl jwtUserDetailsService, JwtTokenUtil jwtTokenUtil, UserRepository userRepository) { this.jwtUserDetailsService = jwtUserDetailsService; this.jwtTokenUtil = jwtTokenUtil; this.userRepository = userRepository; } @Override public String login(String username, String password) throws Exception { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username); return jwtTokenUtil.generateToken(userDetails); } @Override public String register(User user) { String username = user.getUsername(); if (userRepository.findByUsername(username) != null) { return "用户已存在"; } BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String rawPassword = user.getPassword(); user.setPassword(encoder.encode(rawPassword)); user.setDeleted(0); user.setState(0); userRepository.save(user); return "success"; } } 九. 用户控制层,添加用户注册和登录请求,这里的请求都不需要token认证。 @RestController @RequestMapping("/uplus") public class UserController { @Autowired private UserServiceImpl userService; /** * 用户登录 * * @param username 用户名 * @param password 密码 * @return 操作结果 * @throws AuthenticationException 错误信息 */ @PostMapping(value = "/user/login", params = { "username", "password"}) public String getToken(String username, String password) throws Exception { return userService.login(username, password); } /** * 用户注册 * * @param user 用户信息 * @return 操作结果 * @throws AuthenticationException 错误信息 */ @PostMapping(value = "/user/register") public String register(User user) throws AuthenticationException { return userService.register(user); } 十. 添加TestCotroller类,与UserController中请求分开,需要进行验证才能请求。 @RestController @RequestMapping("/uplus") public class TestController { @Autowired private UserServiceImpl userService; @Autowired private UserRepository userRepository; @PostMapping(value = "/test/getInfoById", params = { "id"} ) public User findById(Long id){ User userInfo=this.userService.findById(id); return userInfo; } @PostMapping(value = "/test/findByUsername", params = { "username"} ) public User findByUsername(String username){ User userInfo=this.userRepository.findByUsername(username); return userInfo; } } 十一. postman测试 1) 用户注册接口 (/uplus/user/register), 该接口在放行范围内,所以不需要token认证。 ![这里写图片描述][70] 2)用户登录接口(/uplus/user/login),该接口在放行范围内,同样不需要token认证,最后结果会返回给我们一个token,在之后的其他请求,都需要将其设置在请求头中。 ![这里写图片描述][70 1] 3) 根据用户id获取用户接口(uplus/test/findByUsername),该接口不在放行范围内,所以需要token验证,请求时添加请求头参数,请求正确结果如下: ![这里写图片描述][70 2] 若请求中请求头不带token,则会返回没有授权的结果,如下: ![这里写图片描述][70 3] [70]: /images/20220526/57975b619ac24055b78b7174e7fa46c5.png [70 1]: /images/20220526/dc48af3e0031454a944a3ffa2327919a.png [70 2]: /images/20220526/a10467fa982242a1af6d3b8352f364bd.png [70 3]: /images/20220526/baf800a60f3f4dedbbf06e3272317094.png
还没有评论,来说两句吧...