SpringSecurity:登录

x33g5p2x  于2021-11-30 转载在 Spring  
字(10.2k)|赞(0)|评价(0)|浏览(326)

SpringSecurity:登录

一、登录处理器

Security默认提供的处理器处理的,一般多用于前后端不分离。

Spring SecurityAuthenticationManager用来处理身份认证的请求,处理的结果分两种:

  • 认证成功:结果由AuthenticationSuccessHandler处理
  • 认证失败:结果由AuthenticationFailureHandler处理

Spring Security提供了多个实现于AuthenticationSuccessHandler接口和CustomAuthenticationFailHandler接口的子类,想自定义处理器,可以实现接口,或继承接口的实现类来重写。

1.1 自定义AuthenticationSuccessHandler

AuthenticationSuccessHandler是身份验证成功处理器的接口,其下有多个子类:

  • SavedRequestAwareAuthenticationSuccessHandler:默认的成功处理器,默认验证成功后,跳转到原路径。也可通过defaultSuccessUrl()配置。
  • SimpleUrlAuthenticationSuccessHandlerSavedRequestAwareAuthenticationSuccessHandler的父类,只有指定defaultSuccessUrl()时,才会被调用。作用:清除原路径,使用defaultSuccessUrl()指定的路径。如果直接使用该处理器,则总跳转到根路径。
  • ForwardAuthenticationSuccessHandler:请求重定向。只有指定successForwardUrl时被用到。

要想自定义成功处理器,可以通过实现AuthenticationSuccessHandler接口或继承其子类SavedRequestAwareAuthenticationSuccessHandler来实现:

如果直接返回Json数据时,可以实现AuthenticationSuccessHandler接口:

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        Authentication authentication) 
        throws ServletException, IOException {
      response.setContentType("application/json;charset=UTF-8");
      response.getWriter().append(
              new ObjectMapper().createObjectNode()
                      .put("status", 200)
                      .put("msg", "登录成功")
                      .toString());
    }
}

如果只是在登录认证后,需要处理数据,再跳转回原路径时,可以继承该类:

public class CustomAuthenticationSuccessHandler2 extends SavedRequestAwareAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        Authentication authentication) 
        throws ServletException, IOException {
        // 登录成功后,进行数据处理
        System.out.println("用户登录成功啦!!!");
        String authenticationStr = objectMapper.writeValueAsString(authentication);
        System.out.println("用户登录信息打印:" + authenticationStr);

        //处理完成后,跳转回原请求URL
        super.onAuthenticationSuccess(request, response, authentication);
    }
}

Spring Security默认是使用SavedRequestAwareAuthenticationSuccessHandler,在配置中修改为自定义的AuthenticationSuccessHandler

1.2 自定义AuthenticationFailureHandler

AuthenticationFailureHandler是身份认证失败处理器的接口,其下有多个子类实现:

  • SimpleUrlAuthenticationFailureHandler:默认的失败处理器,默认认证失败后,跳转到登录页路径加error参数,如:http://localhost:8080/login?error。可通过failureUrl()配置
  • ForwardAuthenticationFailureHandler:重定向到指定的URL
  • DelegatingAuthenticationFailureHandler:将AuthenticationException子类委托给不同的AuthenticationFailureHandler,意味着可以为AuthenticationException的不同实例创建不同的行为
  • ExceptionMappingAuthenticationFailureHandler:可以根据不同的AuthenticationException 类型,设置不同的跳转 url

自定义失败处理器,可以通过实现AuthenticationFailureHandler接口或继承其子类SimpleUrlAuthenticationFailureHandler来实现:

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        AuthenticationException exception) 
        throws IOException, ServletException {
      response.setContentType("application/json;charset=UTF-8");
      response.getWriter().append(
              new ObjectMapper().createObjectNode()
                      .put("status", 401)
                      .put("msg", "用户名或密码错误")
                      .toString());
    }
}
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {  
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        AuthenticationException exception) 
        throws IOException, ServletException {
        // 登录失败后,进行数据处理
        System.out.println("登录失败啦!!!");
        String exceptionStr = objectMapper.writeValueAsString(exception.getMessage());
        System.out.println(exceptionStr);

        // 跳转原页面
        super.onAuthenticationFailure(request, response, exception);
    }
}

Spring Security默认验证失败是使用**SimpleUrlAuthenticationFailureHandler**,在配置中修改为自定义的AuthenticationFailureHandler

1.3 DelegatingAuthenticationFailureHandler

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    @Bean
    public DelegatingAuthenticationFailureHandler delegatingAuthenticationFailureHandler(){
        LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers = new LinkedHashMap<>();
        // 登录失败时,使用的失败处理器
        handlers.put(BadCredentialsException.class, new BadCredentialsAuthenticationFailureHandler());
        // 用户过期时,使用的失败处理器
        handlers.put(AccountExpiredException.class, new AccountExpiredAuthenticationFailureHandler());
        // 用户被锁定时,使用的失败处理
        handlers.put(LockedException.class, new LockedAuthenticationFailureHandler());
        return new DelegatingAuthenticationFailureHandler(handlers, new AuthenticationFailureHandler());
    }
    
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
              // 配置使用自定义失败处理器
                .failureHandler(delegatingAuthenticationFailureHandler());
    }
}

1.4 ExceptionMappingAuthenticationFailureHandler

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    @Bean
    public ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler(){
        ExceptionMappingAuthenticationFailureHandler handler = new ExceptionMappingAuthenticationFailureHandler();
        HashMap<String, String> map = new HashMap<>();
        // 登录失败时,跳转到 /badCredentials
        map.put(BadCredentialsException.class.getName(), "/badCredentials");
        // 用户过期时,跳转到 /accountExpired
        map.put(AccountExpiredException.class.getName(), "/accountExpired");
        // 用户被锁定时,跳转到 /locked
        map.put(LockedException.class.getName(), "/locked");
        handler.setExceptionMappings(map);
        return handler;
    }
    
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
              // 配置使用自定义失败处理器
                .failureHandler(exceptionMappingAuthenticationFailureHandler());
    }
}

二、设置登录成功页和失败页

2.1 默认情况下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYZrJm2D-1638256792397)(SpringSecurity:登录.assets/image-20211130142459058.png)]

当我们输入用户名或者密码错误的时候,页面不会显示错误信息,控制台也不报错

这是因为首先 /login?error 是 Spring security 默认的失败 Url,其次如果你不手动处理这个异常,这个异常是不会被处理的

2.2 指定URL处理

指定错误Url,WebSecurityConfig中添加.failureUrl("/login/error")

//设置登录操作
        http.formLogin()
                //设置登陆页
                .loginPage("/login")
                .defaultSuccessUrl("/")
                //设置登录失败页
                .failureUrl("/login/error")
                //设置登录成功页
                .defaultSuccessUrl("/")
                .permitAll();

在Controller中处理异常

@RequestMapping("/login/error")
public void loginError(HttpServletRequest request, HttpServletResponse response) {
    response.setContentType("text/html;charset=utf-8");
    AuthenticationException exception =
            (AuthenticationException)request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
    try {
        response.getWriter().write(exception.toString());
    }catch (IOException e) {
        e.printStackTrace();
    }
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jqi0niNw-1638256792399)(SpringSecurity:登录.assets/image-20211130142941090.png)]

2.3 自定义认证成功、失败处理

关于成功和失败的处理,前面我们是用failureUrl()来指定失败后的URL,defaultSuccessUrl() 指定认证成功后URL,我们可以通过设置 successHandler()failureHandler() 来实现自定义认证成功、失败处理。

CustomAuthenticationSuccessHandler:
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功,{}", authentication);
        response.sendRedirect("/");
    }
}

onAuthenticationSuccess() 方法的第三个参数 Authentication 为认证后该用户的认证信息,这里打印日志后,重定向到了首页。

CustomAuthenticationFailureHandler:
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        logger.info("登陆失败");

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
    }
}

onAuthenticationFailure()方法的第三个参数 exception 为认证失败所产生的异常,这里也是简单的返回到前台。

修改 WebSecurityConfig:

首先需要把我们自定义的类注入进来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUPN4p8O-1638256792400)(SpringSecurity:登录.assets/image-20211130143606069.png)]

这俩种方式只能存在一种

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4nt9jfs-1638256792401)(SpringSecurity:登录.assets/image-20211130143822194.png)]

三、认证入口

AuthenticationEntryPointSpring Security认证入口点接口,在用户请求处理过程中遇到认证异常时,使用特定认证方式进行认证。

AuthenticationEntryPoint内置实现类:

  • LoginUrlAuthenticationEntryPoint:根据配置的登录页面url,将用户重定向到该登录页面进行认证。默认的认证方式。
  • Http403ForbiddenEntryPoint:设置响应状态为403,不触发认证。通常在预身份认证中设置
  • HttpStatusEntryPoint:设置特定的响应状态码,不触发认证。
  • BasicAuthenticationEntryPoint:设置基本(Http Basic)认证,在响应状态码401HeaderWWW-Authenticate:"Basic realm="xxx"时使用。
  • DigestAuthenticationEntryPoint:设置摘要(Http Digest)认证,在响应状态码401HeaderWWW-Authenticate:"Digest realm="xxx"时使用。
  • DelegatingAuthenticationEntryPoint:根据匹配URI来委托给不同的AuthenticationEntryPoint,且必须制定一个默认的认证方式。

3.1 AuthenticationEntryPoint的使用

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    @Bean
    public DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
        LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = new LinkedHashMap<>();
        // GET方式请求/test时,直接返回 403
        map.put(new AntPathRequestMatcher("/test", "GET"), new Http403ForbiddenEntryPoint());
        // 访问 /basic时,直接返回 400 bad request
        map.put(new AntPathRequestMatcher("/basic"), 
                new HttpStatusEntryPoint(HttpStatus.BAD_REQUEST));
        DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(map);
        // 除了上面两个 uri 配置指定的认证入口,其它默认使用 LoginUrlAuthenticationEntryPoint认证入口
        entryPoint.setDefaultEntryPoint(new LoginUrlAuthenticationEntryPoint("/user-login"));
        return entryPoint;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /** * Http403ForbiddenEntryPoint 用法 */
        // http.exceptionHandling()
        // .authenticationEntryPoint(new Http403ForbiddenEntryPoint());
        /** * HttpStatusEntryPoint 用法 */
        // http.exceptionHandling()
        // .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.BAD_REQUEST));
        /** * DelegatingAuthenticationEntryPoint 用法 */
        http.exceptionHandling()
            .authenticationEntryPoint(delegatingAuthenticationEntryPoint());
        ...
    }
}

相关文章