在spring security登录中增加额外的用户需求并处理各种异常

Posted

技术标签:

【中文标题】在spring security登录中增加额外的用户需求并处理各种异常【英文标题】:Add additional user requirements in spring security login and handle various exceptions 【发布时间】:2021-11-04 18:48:28 【问题描述】:

我是 Spring 安全的新手,我已经使用 JWT 为我的应用程序实现了基本的用户登录功能。除了在登录时检查用户名和密码之外,我还想添加其他参数,例如“帐户已验证”布尔条件,但我不确定在哪里添加此要求。此外,如果“帐户已验证”条件为假,我需要返回 403 禁止响应状态消息,如果根本找不到用户名密码组合,则返回不同的响应状态消息。这是我目前拥有的代码,它正确处理现有用户的登录(不检查“帐户已验证”条件),并在找到用户时始终抛出 401。任何反馈都会有所帮助。

WebSecurityConfigurerAdapter

public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    private final ApplicationUserDetailsService applicationUserDetailsService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurityConfig(ApplicationUserDetailsService userDetailsService) 
        this.applicationUserDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
    

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
        httpSecurity
          .cors()
          .and()
          .csrf()
          .disable()
          .authorizeRequests()
          .antMatchers("/**")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .addFilter(new AuthenticationFilter(authenticationManager()))
          .addFilter(new AuthorizationFilter(authenticationManager()))
          .sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    

    @Bean
    public PasswordEncoder encoder() 
        return this.bCryptPasswordEncoder;
    

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(applicationUserDetailsService)
                .passwordEncoder(bCryptPasswordEncoder);
    

用户详细信息服务

public class ApplicationUserDetailsService implements UserDetailsService 

    private final ApplicationUserRepository applicationUserRepository;

    public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository) 
        this.applicationUserRepository = applicationUserRepository;
    

    @Override
    public UserDetails loadUserByUsername(String nickname)
            throws UsernameNotFoundException, UserIsNotActiveException 
        Optional<ApplicationUser> applicationUser =
                applicationUserRepository.findByNickname(nickname);
        if (!applicationUser.isPresent()) 
            throw new UsernameNotFoundException(nickname);
        

        return new User(
                applicationUser.get().getNickname(),
                applicationUser.get().getPassword(),
                emptyList());
    

身份验证过滤器

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter 
    private AuthenticationManager authenticationManager;

    public AuthenticationFilter(AuthenticationManager authenticationManager) 
        this.authenticationManager = authenticationManager;
    

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException 
        try 
            ApplicationUser applicationUser =
                    new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            applicationUser.getNickname(),
                            applicationUser.getPassword(),
                            new ArrayList<>()));

         catch (IOException e) 
            throw new RuntimeException(e);
        
    

    @Override
    protected void successfulAuthentication(
            HttpServletRequest req,
            HttpServletResponse res,
            FilterChain chain,
            Authentication auth) 
        Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);

        Key key = Keys.hmacShaKeyFor(KEY.getBytes());
        Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
        String token =
                Jwts.builder()
                        .setClaims(claims)
                        .signWith(key, SignatureAlgorithm.HS512)
                        .setExpiration(exp)
                        .compact();
        res.addHeader("token", token);
    

授权过滤器

public AuthorizationFilter(AuthenticationManager authManager) 
    super(authManager);


@Override
protected void doFilterInternal(
        HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException 
    String header = request.getHeader(HEADER_NAME);

    if (header == null) 
        chain.doFilter(request, response);
        return;
    

    UsernamePasswordAuthenticationToken authentication = authenticate(request);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(request, response);


private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request) 
    String token = request.getHeader(HEADER_NAME);
    if (token != null) 
        Jws<Claims> user =
                Jwts.parserBuilder()
                        .setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
                        .build()
                        .parseClaimsJws(token);

        if (user != null) 
            return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
         else 
            return null;
        
    
    return null;

应用用户

public class ApplicationUser 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    @Column(unique = true)
    String email;

    @Column(unique = true)
    String nickname;

    String biography;

    String password; // Hashed

    @Builder.Default boolean isActive = false;

【问题讨论】:

【参考方案1】:

接口UserDetails(由UserDetailsService 返回)有一些实用方法可以帮助您。

在帐户未激活时,您可以通过UserDetails#isEnabled 方法返回false,或者您也可以使用UserDetails#isAccountNonLocked

这些方法将在 AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks 类上自动验证。

用户完成激活流程后,您可以将属性更改为true,它将允许用户进行身份验证。

提示:将logging.level.org.springframework.security=TRACE 添加到您的application.properties 以帮助调试。

【讨论】:

以上是关于在spring security登录中增加额外的用户需求并处理各种异常的主要内容,如果未能解决你的问题,请参考以下文章

如何限制 Spring Security 中的登录尝试?

相同 API 的 Spring Security Basic Auth 和 Form 登录

spring security remember me实现自动登录

单点登录系统使用Spring Security的权限功能

Spring Security 多登录实现

Spring Security应用开发(09)密码错误次数限制