Spring Security | Word count: 1.9k | Reading time: 7min | Post View:
版本:
登录 仅仅只是在pom文件中添加springboot security依赖之后,前端就不能再和后端交互了,出现了跨域问题。
想要通过security实现登录,需更改三个地方。
用户校验 security默认使用了InMemoryUserDetailsManager来对用户密码进行校验,它使用的是自己生成的一份用户名和密码,而我们想要其通过用户名从数据库中查询用户的密码,然后和前端发来的密码做匹配。为了完成此功能,需要创建一个继承UserDetailsService接口的类,重写loadUserByUsername方法。
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 @Service public class UserDetailsServiceImpl implements UserDetailsService { private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); @Autowired private SysUserService userService; @Autowired private SysPasswordService passwordService; @Autowired private SysPermissionService permissionService; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { SysUser user = userService.selectUserByUserName(username); if (StringUtils.isNull(user)) { log.info("登录用户:{} 不存在." , username); throw new ServiceException("登录用户:" + username + " 不存在" ); } passwordService.validate(user); return createLoginUser(user); } public UserDetails createLoginUser (SysUser user) { return new LoginUser(user.getUserId(), user.getDeptId(), user, Collections.emptySet()); } }
自定义登录接口 为了能够调用loadUserByUsername,需要显示的调用AuthenticationManager类的authenticate方法。authenticate方法接收一个类型为Authentication的参数,而该类型是一个接口,无法创建该类型的对象进行函数传递,一般使用其实现类UsernamePasswordAuthenticationToken。
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 @Resource private AuthenticationManager authenticationManager; public String login (String username, String password, String uuid) { loginPreCheck(username, password); Authentication authentication = null ; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { throw new UserPasswordNotMatchException(); } else { throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } LoginUser loginUser = (LoginUser) authentication.getPrincipal(); return tokenService.createToken(loginUser); }
为了能够自动注入AuthenticationManager类,需要再写一个函数。
1 2 3 4 @Bean public AuthenticationManager authenticationManagerBean (AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }
过滤链 security默认会阻止任何未通过认证的请求,需要配置过滤器链,告知在访问哪些路由时不需要验证,比如可以匿名访问登录页面和静态资源。需要创建一个类来对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 @Configuration(proxyBeanMethods = false) @EnableWebSecurity public class WebSecurityConfig { @Autowired private AuthenticationEntryPointImpl unauthorizedHandler; @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .csrf().disable() .headers().cacheControl().disable().and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers("/login" , "/register" ).permitAll() .antMatchers(HttpMethod.GET, "/" , "/*.html" , "/**/*.html" , "/**/*.css" , "/**/*.js" , "/profile/**" ).permitAll() .antMatchers("/swagger-ui.html" , "/swagger-resources/**" , "/webjars/**" , "/*/api-docs" , "/druid/**" ).permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable(); return http.build(); } }
为了支持对访问其他路由的请求的鉴权认证,需要设置认证过滤器,将根据用户创建的Authentication类型的实例放入到SecurityContextHolder中,以便其他过滤器使用,放行已认证的用户发来的请求。
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 @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { LoginUser loginUser = tokenService.getLoginUser(request); if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null , loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } }
将该过滤器添加到过滤器链中
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 @Configuration(proxyBeanMethods = false) @EnableWebSecurity public class WebSecurityConfig { @Autowired private AuthenticationEntryPointImpl unauthorizedHandler; @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; @Bean public AuthenticationManager authenticationManagerBean (AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .csrf().disable() .headers().cacheControl().disable().and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers("/login" , "/register" ).permitAll() .antMatchers(HttpMethod.GET, "/" , "/*.html" , "/**/*.html" , "/**/*.css" , "/**/*.js" , "/profile/**" ).permitAll() .antMatchers("/swagger-ui.html" , "/swagger-resources/**" , "/webjars/**" , "/*/api-docs" , "/druid/**" ).permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable(); http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder () { return new BCryptPasswordEncoder(); } }
参考:Springsecurity2.7+jwt最新版教程 | bilibili