图形验证码
SpringSecurity 实现的用户名、密码登录是在 UsernamePasswordAuthenticationFilter 过滤器进行认证的,而图形验证码一样平常是在用户名、密码认证之前进行验证的,所以须要在 UsernamePasswordAuthenticationFilter 过滤器之前添加一个自定义过滤器 ImageCodeValidateFilter,用来校验用户输入的图形验证码是否正确。
实现逻辑
自定义过滤器继承 OncePerRequestFilter 类,该类是 Spring 提供的在一次哀求中只会调用一次的 filter,确保每个哀求只会进入过滤器一次,避免了多次执行的情况
自定义的过滤器 ImageCodeValidateFilter 首先会判断哀求是否为 POST 方式的登录表单提交哀求,如果是就将其拦截进行图形验证码校验。如果验证错误,会抛出自定义异常类对象 ValidateCodeException,该异常类须要继承 AuthenticationException 类。在自定义过滤器中,我们须要手动捕获自定义异常类对象,并将捕获到自定义异常类对象交给自定义失败处理器进行处理
添加验证码配置
- <dependency>
- <groupId>com.github.penggle</groupId>
- <artifactId>kaptcha</artifactId>
- <version>2.3.2</version>
- </dependency>
复制代码- /**
- * 图形验证码的配置类
- */
- @Configuration
- public class KaptchaConfig {
- @Bean
- public DefaultKaptcha captchaProducer() {
- DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
- Properties properties = new Properties();
- // 是否有边框
- properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
- // 边框颜色
- properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
- // 验证码图片的宽和高
- properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
- properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "40");
- // 验证码颜色
- properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "0,0,0");
- // 验证码字体大小
- properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "32");
- // 验证码生成几个字符
- properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
- // 验证码随机字符库
- properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
- // 验证码图片默认是有线条干扰的,我们设置成没有干扰
- properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
- Config config = new Config(properties);
- defaultKaptcha.setConfig(config);
- return defaultKaptcha;
- }
- }
复制代码 提供验证码接口
- public class CheckCode implements Serializable {
- private String code; // 验证码字符
- private LocalDateTime expireTime; // 过期时间
- /**
- * @param code 验证码字符
- * @param expireTime 过期时间,单位秒
- */
- public CheckCode(String code, int expireTime) {
- this.code = code;
- this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
- }
- public CheckCode(String code) {
- // 默认验证码 60 秒后过期
- this(code, 60);
- }
- // 是否过期
- public boolean isExpried() {
- return this.expireTime.isBefore(LocalDateTime.now());
- }
- public String getCode() {
- return this.code;
- }
- }
复制代码- @Controller
- public class LoginController {
- // Session 中存储图形验证码的属性名
- public static final String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
- @Autowired
- private DefaultKaptcha defaultKaptcha;
- @GetMapping("/login/page")
- public String login() {
- return "login";
- }
- @GetMapping("/code/image")
- public void imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
- // 创建验证码文本
- String capText = defaultKaptcha.createText();
- // 创建验证码图片
- BufferedImage image = defaultKaptcha.createImage(capText);
- // 将验证码文本放进 Session 中
- CheckCode code = new CheckCode(capText);
- request.getSession().setAttribute(KAPTCHA_SESSION_KEY, code);
- // 将验证码图片返回,禁止验证码图片缓存
- response.setHeader("Cache-Control", "no-store");
- response.setHeader("Pragma", "no-cache");
- response.setDateHeader("Expires", 0);
- response.setContentType("image/jpeg");
- ImageIO.write(image, "jpg", response.getOutputStream());
- }
- }
复制代码- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>登录</title>
- </head>
- <body>
- <h3>表单登录</h3>
- <form method="post" th:action="@{/login/form}">
- <input type="text" name="username" placeholder="用户名">
- <input type="password" name="password" placeholder="密码">
- <input name="imageCode" type="text" placeholder="验证码">
- <img th:onclick="this.src='/code/image?'+Math.random()" th:src="@{/code/image}" alt="验证码"/>
-
- 用户名或密码错误
-
- <button type="submit">登录</button>
- </form>
- </body>
- </html>
复制代码 自定义验证码过滤器
- /**
- * 自定义验证码校验错误的异常类,继承 AuthenticationException
- */
- public class ValidateCodeException extends AuthenticationException {
- public ValidateCodeException(String msg, Throwable t) {
- super(msg, t);
- }
- public ValidateCodeException(String msg) {
- super(msg);
- }
- }
复制代码- @Component
- public class ImageCodeValidateFilter extends OncePerRequestFilter {
- private String codeParamter = "imageCode"; // 前端输入的图形验证码参数名
- @Autowired
- private AuthenticationFailureHandlerImpl authenticationFailureHandler; // 自定义认证失败处理器
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- // 非 POST 方式的表单提交请求不校验图形验证码
- if ("/login/form".equals(request.getRequestURI()) && "POST".equals(request.getMethod())) {
- try {
- // 校验图形验证码合法性
- validate(request);
- } catch (ValidateCodeException e) {
- // 手动捕获图形验证码校验过程抛出的异常,将其传给失败处理器进行处理
- authenticationFailureHandler.onAuthenticationFailure(request, response, e);
- return;
- }
- }
- // 放行请求,进入下一个过滤器
- filterChain.doFilter(request, response);
- }
- // 判断验证码的合法性
- private void validate(HttpServletRequest request) {
- // 获取用户传入的图形验证码值
- String requestCode = request.getParameter(this.codeParamter);
- if(requestCode == null) {
- requestCode = "";
- }
- requestCode = requestCode.trim();
- // 获取 Session
- HttpSession session = request.getSession();
- // 获取存储在 Session 里的验证码值
- CheckCode savedCode = (CheckCode) session.getAttribute(LoginController.KAPTCHA_SESSION_KEY);
- if (savedCode != null) {
- // 随手清除验证码,无论是失败,还是成功。客户端应在登录失败时刷新验证码
- session.removeAttribute(LoginController.KAPTCHA_SESSION_KEY);
- }
- // 校验出错,抛出异常
- if (StringUtils.isBlank(requestCode)) {
- throw new ValidateCodeException("验证码的值不能为空");
- }
- if (savedCode == null) {
- throw new ValidateCodeException("验证码不存在");
- }
- if (savedCode.isExpried()) {
- throw new ValidateCodeException("验证码过期");
- }
- if (!requestCode.equalsIgnoreCase(savedCode.getCode())) {
- throw new ValidateCodeException("验证码输入错误");
- }
- }
- }
复制代码- @Component
- public class UserDetailServiceImpl implements UserDetailsService {
- @Autowired
- private PasswordEncoder passwordEncoder;
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- if ("root".equals(username)) {
- return new User(username, passwordEncoder.encode("123"), AuthorityUtils.createAuthorityList("admin"));
- } else {
- throw new UsernameNotFoundException("用户名不存在");
- }
- }
- }
复制代码 设置过滤器次序
- @Configuration
- public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private AuthenticationSuccessHandlerImpl authenticationSuccessHandler;
- @Autowired
- private AuthenticationFailureHandlerImpl authenticationFailureHandler;
- @Autowired
- private ImageCodeValidateFilter imageCodeValidateFilter; // 自定义过滤器(图形验证码校验)
- @Autowired
- private UserDetailServiceImpl userDetailService;
- /**
- * 密码编码器,密码不能明文存储
- */
- @Bean
- public BCryptPasswordEncoder passwordEncoder() {
- // 使用 BCryptPasswordEncoder 密码编码器,该编码器会将随机产生的 salt 混入最终生成的密文中
- return new BCryptPasswordEncoder();
- }
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
- }
- /**
- * 定制基于 HTTP 请求的用户访问控制
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- // 启动 form 表单登录
- http.formLogin()
- // 设置登录页面的访问路径,默认为 /login,GET 请求;该路径不设限访问
- .loginPage("/login/page")
- // 设置登录表单提交路径,默认为 loginPage() 设置的路径,POST 请求
- .loginProcessingUrl("/login/form")
- // 使用自定义的认证成功和失败处理器
- .successHandler(authenticationSuccessHandler)
- .failureHandler(authenticationFailureHandler);
- // 开启基于 HTTP 请求访问控制
- http.authorizeRequests()
- // 以下访问不需要任何权限,任何人都可以访问
- .antMatchers("/login/page", "/code/image").permitAll()
- // 其它任何请求访问都需要先通过认证
- .anyRequest().authenticated();
- // 关闭 csrf 防护
- http.csrf().disable();
- // 将自定义过滤器(图形验证码校验)添加到 UsernamePasswordAuthenticationFilter 之前
- http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
- }
- /**
- * 定制一些全局性的安全配置,例如:不拦截静态资源的访问
- */
- @Override
- public void configure(WebSecurity web) throws Exception {
- // 静态资源的访问不需要拦截,直接放行
- web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
- }
- }
复制代码 手机短信验证码
验证流程
带有图形验证码的用户名、密码登录流程:
- 在 ImageCodeValidateFilter 过滤器中校验用户输入的图形验证码是否正确。
- 在 UsernamePasswordAuthenticationFilter 过滤器中将 username 和 password 天生一个用于认证的 Token(UsernamePasswordAuthenticationToken),并将其传递给 ProviderManager 接口的实现类 AuthenticationManager。
- AuthenticationManager 管理器寻找到一个合适的处理器 DaoAuthenticationProvider 来处理 UsernamePasswordAuthenticationToken。
- DaoAuthenticationProvider 通过 UserDetailsService 接口的实现类 CustomUserDetailsService 从数据库中获取指定 username 的相关信息,并校验用户输入的 password。如果校验乐成,那就认证通过,用户信息类对象 Authentication 标志为已认证。
- 认证通过后,将已认证的用户信息对象 Authentication 存储到 SecurityContextHolder 中,最终存储到 Session 中。
仿照上述流程,我们分析手机短信验证码登录流程:
- 仿照 ImageCodeValidateFilter 过滤器设计 MobileVablidateFilter 过滤器,该过滤器用来校验用户输入手机短信验证码。
- 仿照 UsernamePasswordAuthenticationFilter 过滤器设计 MobileAuthenticationFilter 过滤器,该过滤器将用户输入的手机号天生一个 Token(MobileAuthenticationToken),并将其传递给 ProviderManager 接口的实现类 AuthenticationManager。
- AuthenticationManager 管理器寻找到一个合适的处理器 MobileAuthenticationProvider 来处理 MobileAuthenticationToken,该处理器是仿照 DaoAuthenticationProvider 进行设计的。
- MobileAuthenticationProvider 通过 UserDetailsService 接口的实现类 MobileUserDetailsService 从数据库中获取指定手机号对应的用户信息,此处不须要进行任何校验,直接将用户信息类对象 Authentication 标志为已认证。
- 认证通过后,将已认证的用户信息对象 Authentication 存储到 SecurityContextHolder 中,最终存储到 Session 中,此处的操作不须要我们编写。
末了通过自定义配置类 MobileAuthenticationConfig 组合上述组件,并添加到安全配置类 SpringSecurityConfig 中。
提供短信发送接口
- @Controller
- public class LoginController {
- // Session 中存储手机短信验证码的属性名
- public static final String MOBILE_SESSION_KEY = "MOBILE_SESSION_KEY";
- @GetMapping("/mobile/page")
- public String mobileLoginPage() { // 跳转到手机短信验证码登录页面
- return "login-mobile";
- }
- @GetMapping("/code/mobile")
- @ResponseBody
- public Object sendMoblieCode(HttpServletRequest request) {
- // 随机生成一个 4 位的验证码
- String code = RandomStringUtils.randomNumeric(4);
- // 将手机验证码文本存储在 Session 中,设置过期时间为 10 * 60s
- CheckCode mobileCode = new CheckCode(code, 10 * 60);
- request.getSession().setAttribute(MOBILE_SESSION_KEY, mobileCode);
- return mobileCode;
- }
- }
复制代码- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>登录页面</title>
-
- </head>
- <body>
- <form method="post" th:action="@{/mobile/form}">
- <input id="mobile" name="mobile" type="text" placeholder="手机号码">
-
- <input name="mobileCode" type="text" placeholder="验证码">
- <button type="button" id="sendCode">获取验证码</button>
-
-
- 用户名或密码错误
-
- <button type="submit">登录</button>
- </form>
-
- </body>
- </html>
复制代码 自定义短信验证码校验过滤器
更改自定义失败处理器 CustomAuthenticationFailureHandler,原先的处理器在认证失败时,会直接重定向到/login/page?error 显示认证异常信息。现在我们有两种登录方式,应该进行以下处理:
- 带图形验证码的用户名、密码方式登录方式出现认证异常,重定向到/login/page?error
- 手机短信验证码方式登录出现认证异常,重定向到/mobile/page?error
- /**
- * 继承 SimpleUrlAuthenticationFailureHandler 处理器,该类是 failureUrl() 方法使用的认证失败处理器
- */
- @Component
- public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
- @Override
- public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
- String xRequestedWith = request.getHeader("x-requested-with");
- // 判断前端的请求是否为 ajax 请求
- if ("XMLHttpRequest".equals(xRequestedWith)) {
- // 认证成功,响应 JSON 数据
- response.setContentType("application/json;charset=utf-8");
- response.getWriter().write("认证失败");
- }else {
- // 用户名、密码方式登录出现认证异常,需要重定向到 /login/page?error
- // 手机短信验证码方式登录出现认证异常,需要重定向到 /mobile/page?error
- // 使用 Referer 获取当前登录表单提交请求是从哪个登录页面(/login/page 或 /mobile/page)链接过来的
- String refer = request.getHeader("Referer");
- String lastUrl = StringUtils.substringBefore(refer, "?");
- // 设置默认的重定向路径
- super.setDefaultFailureUrl(lastUrl + "?error");
- // 调用父类的 onAuthenticationFailure() 方法
- super.onAuthenticationFailure(request, response, e);
- }
- }
- }
复制代码- /**
- * 手机短信验证码校验
- */
- @Component
- public class MobileCodeValidateFilter extends OncePerRequestFilter {
- private String codeParamter = "mobileCode"; // 前端输入的手机短信验证码参数名
- @Autowired
- private CustomAuthenticationFailureHandler authenticationFailureHandler; // 自定义认证失败处理器
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- // 非 POST 方式的手机短信验证码提交请求不进行校验
- if("/mobile/form".equals(request.getRequestURI()) && "POST".equals(request.getMethod())) {
- try {
- // 检验手机验证码的合法性
- validate(request);
- } catch (ValidateCodeException e) {
- // 将异常交给自定义失败处理器进行处理
- authenticationFailureHandler.onAuthenticationFailure(request, response, e);
- return;
- }
- }
- // 放行,进入下一个过滤器
- filterChain.doFilter(request, response);
- }
- /**
- * 检验用户输入的手机验证码的合法性
- */
- private void validate(HttpServletRequest request) {
- // 获取用户传入的手机验证码值
- String requestCode = request.getParameter(this.codeParamter);
- if(requestCode == null) {
- requestCode = "";
- }
- requestCode = requestCode.trim();
- // 获取 Session
- HttpSession session = request.getSession();
- // 获取 Session 中存储的手机短信验证码
- CheckCode savedCode = (CheckCode) session.getAttribute(LoginController.MOBILE_SESSION_KEY);
- if (savedCode != null) {
- // 随手清除验证码,无论是失败,还是成功。客户端应在登录失败时刷新验证码
- session.removeAttribute(LoginController.MOBILE_SESSION_KEY);
- }
- // 校验出错,抛出异常
- if (StringUtils.isBlank(requestCode)) {
- throw new ValidateCodeException("验证码的值不能为空");
- }
- if (savedCode == null) {
- throw new ValidateCodeException("验证码不存在");
- }
- if (savedCode.isExpried()) {
- throw new ValidateCodeException("验证码过期");
- }
- if (!requestCode.equalsIgnoreCase(savedCode.getCode())) {
- throw new ValidateCodeException("验证码输入错误");
- }
- }
- }
复制代码 自定义短信验证码认证过滤器
- 仿照 UsernamePasswordAuthenticationToken 类进行编写
- 仿照 UsernamePasswordAuthenticationFilter 过滤器进行编写
- public class MobileAuthenticationToken extends AbstractAuthenticationToken {
- private static final long serialVersionUID = 520L;
- private final Object principal;
- /**
- * 认证前,使用该构造器进行封装信息
- */
- public MobileAuthenticationToken(Object principal) {
- super(null); // 用户权限为 null
- this.principal = principal; // 前端传入的手机号
- this.setAuthenticated(false); // 标记为未认证
- }
- /**
- * 认证成功后,使用该构造器封装用户信息
- */
- public MobileAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
- super(authorities); // 用户权限集合
- this.principal = principal; // 封装认证用户信息的 UserDetails 对象,不再是手机号
- super.setAuthenticated(true); // 标记认证成功
- }
- @Override
- public Object getCredentials() {
- // 由于使用手机短信验证码登录不需要密码,所以直接返回 null
- return null;
- }
- @Override
- public Object getPrincipal() {
- return this.principal;
- }
- @Override
- public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
- if (isAuthenticated) {
- throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
- } else {
- super.setAuthenticated(false);
- }
- }
- @Override
- public void eraseCredentials() {
- // 手机短信验证码认证方式不必去除额外的敏感信息,所以直接调用父类方法
- super.eraseCredentials();
- }
- }
复制代码- /**
- * 手机短信验证码认证过滤器,仿照 UsernamePasswordAuthenticationFilter 过滤器编写
- */
- public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
- private String mobileParamter = "mobile"; // 默认手机号参数名为 mobile
- private boolean postOnly = true; // 默认请求方式只能为 POST
- protected MobileAuthenticationFilter() {
- // 默认登录表单提交路径为 /mobile/form,POST 方式请求
- super(new AntPathRequestMatcher("/mobile/form", "POST"));
- }
- @Override
- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
- //(1) 默认情况下,如果请求方式不是 POST,会抛出异常
- if(postOnly && !request.getMethod().equals("POST")) {
- throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
- }else {
- //(2) 获取请求携带的 mobile
- String mobile = request.getParameter(mobileParamter);
- if(mobile == null) {
- mobile = "";
- }
- mobile = mobile.trim();
- //(3) 使用前端传入的 mobile 构造 Authentication 对象,标记该对象未认证
- // MobileAuthenticationToken 是我们自定义的 Authentication 类,后续介绍
- MobileAuthenticationToken authRequest = new MobileAuthenticationToken(mobile);
- //(4) 将请求中的一些属性信息设置到 Authentication 对象中,如:remoteAddress,sessionId
- this.setDetails(request, authRequest);
- //(5) 调用 ProviderManager 类的 authenticate() 方法进行身份认证
- return this.getAuthenticationManager().authenticate(authRequest);
- }
- }
- @Nullable
- protected String obtainMobile(HttpServletRequest request) {
- return request.getParameter(this.mobileParamter);
- }
- protected void setDetails(HttpServletRequest request, MobileAuthenticationToken authRequest) {
- authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
- }
- public void setMobileParameter(String mobileParamter) {
- Assert.hasText(mobileParamter, "Mobile par ameter must not be empty or null");
- this.mobileParamter = mobileParamter;
- }
- public void setPostOnly(boolean postOnly) {
- this.postOnly = postOnly;
- }
- public String getMobileParameter() {
- return mobileParamter;
- }
- }
复制代码 自定义短信验证码认证方式配置类
- 将上述组件进行管理,仿照 SecurityConfigurerAdapter类进行编写
- 绑定到最终的安全配置类 SpringSecurityConfig 中
- public class MobileAuthenticationProvider implements AuthenticationProvider {
- private UserDetailsService userDetailsService;
- protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
- private UserDetailsChecker authenticationChecks = new MobileAuthenticationProvider.DefaultAuthenticationChecks();
- /**
- * 处理认证
- */
- @Override
- public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- //(1) 如果入参的 Authentication 类型不是 MobileAuthenticationToken,抛出异常
- Assert.isInstanceOf(MobileAuthenticationToken.class, authentication, () -> {
- return this.messages.getMessage("MobileAuthenticationProvider.onlySupports", "Only MobileAuthenticationToken is supported");
- });
- // 获取手机号
- String mobile = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
- //(2) 根据手机号从数据库中查询用户信息
- UserDetails user = this.userDetailsService.loadUserByUsername(mobile);
- if (user == null) {
- //(3) 未查询到用户信息,抛出异常
- throw new AuthenticationServiceException("该手机号未注册");
- }
- //(4) 检查账号是否锁定、账号是否可用、账号是否过期、密码是否过期
- this.authenticationChecks.check(user);
- //(5) 查询到了用户信息,则认证通过,构建标记认证成功用户信息类对象 AuthenticationToken
- MobileAuthenticationToken result = new MobileAuthenticationToken(user, user.getAuthorities());
- // 需要把认证前 Authentication 对象中的 details 信息加入认证后的 Authentication
- result.setDetails(authentication.getDetails());
- return result;
- }
- /**
- * ProviderManager 管理器通过此方法来判断是否采用此 AuthenticationProvider 类
- * 来处理由 AuthenticationFilter 过滤器传入的 Authentication 对象
- */
- @Override
- public boolean supports(Class<?> authentication) {
- // isAssignableFrom 返回 true 当且仅当调用者为父类.class,参数为本身或者其子类.class
- // ProviderManager 会获取 MobileAuthenticationFilter 过滤器传入的 Authentication 类型
- // 所以当且仅当 authentication 的类型为 MobileAuthenticationToken 才返回 true
- return MobileAuthenticationToken.class.isAssignableFrom(authentication);
- }
- /**
- * 此处传入自定义的 MobileUserDetailsSevice 对象
- */
- public void setUserDetailsService(UserDetailsService userDetailsService) {
- this.userDetailsService = userDetailsService;
- }
- public UserDetailsService getUserDetailsService() {
- return userDetailsService;
- }
- /**
- * 检查账号是否锁定、账号是否可用、账号是否过期、密码是否过期
- */
- private class DefaultAuthenticationChecks implements UserDetailsChecker {
- private DefaultAuthenticationChecks() {
- }
- @Override
- public void check(UserDetails user) {
- if (!user.isAccountNonLocked()) {
- throw new LockedException(MobileAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
- } else if (!user.isEnabled()) {
- throw new DisabledException(MobileAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
- } else if (!user.isAccountNonExpired()) {
- throw new AccountExpiredException(MobileAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
- } else if (!user.isCredentialsNonExpired()) {
- throw new CredentialsExpiredException(MobileAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
- }
- }
- }
- }
复制代码- @Service
- public class MobileUserDetailsService implements UserDetailsService {
- @Autowired
- private UserMapper userMapper;
- @Override
- public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
- //(1) 从数据库尝试读取该用户
- User user = userMapper.selectByMobile(mobile);
- // 用户不存在,抛出异常
- if (user == null) {
- throw new UsernameNotFoundException("用户不存在");
- }
- //(2) 将数据库形式的 roles 解析为 UserDetails 的权限集合
- // AuthorityUtils.commaSeparatedStringToAuthorityList() 是 Spring Security 提供的方法,用于将逗号隔开的权限集字符串切割为可用权限对象列表
- user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
- //(3) 返回 UserDetails 对象
- return user;
- }
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |