用于身份验证错误Spring Security的Spring Boot自定义消息

时间:2019-05-03 21:37:35

标签: spring-boot spring-security jwt

我想在身份验证失败时发送自定义错误消息

我正在使用以下方法来验证用户是否存在于数据库中以生成jwt,如果验证失败,它将返回401错误,但收到的响应为空,我想在消息中添加原因取决于错误。


@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, LockedException {

                final AppUser user = userService.findByUsername(username);
        if (user != null) {
            if(user.isEnabled()) {
                List<GrantedAuthority> grantedAuthorities = AuthorityUtils
                        .commaSeparatedStringToAuthorityList("ROLE_" + user.getRole());


                return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
            }else{
                throw new LockedException("Username: " + username + " is Locked");
            }

        }
        // If user not found. Throw this exception.
        throw new UsernameNotFoundException("Username: " + username + " not found");
    }


这是安全配置:

@EnableWebSecurity  // Enable security config. This annotation denotes config for spring security.
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtConfig jwtConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()

                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()

                .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED,"your message goes here"))
            .and()

            .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig))  
        .authorizeRequests()
            // allow all POST requests 
            .antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
            .antMatchers(HttpMethod.POST, jwtConfig.getSignUpUri()).permitAll()
            .antMatchers(HttpMethod.GET, jwtConfig.getValidateEmailUri()).permitAll()
            // any other requests must be authenticated
            .anyRequest().authenticated();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public JwtConfig jwtConfig() {
            return new JwtConfig();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这是jwtfilter:


public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter   {

    // We use auth manager to validate the user credentials
    private AuthenticationManager authManager;

    private final JwtConfig jwtConfig;

    public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authManager, JwtConfig jwtConfig) {
        this.authManager = authManager;
        this.jwtConfig = jwtConfig;

        // By default, UsernamePasswordAuthenticationFilter listens to "/login" path. 
        // In our case, we use "/auth". So, we need to override the defaults.
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(jwtConfig.getUri(), "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        try {

            // 1. Get credentials from request
            UserCredentials creds = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class);

            // 2. Create auth object (contains credentials) which will be used by auth manager
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    creds.getUsername(), creds.getPassword(), Collections.emptyList());

            // 3. Authentication manager authenticate the user, and use UserDetialsServiceImpl::loadUserByUsername() method to load the user.
            return authManager.authenticate(authToken);

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

    // Upon successful authentication, generate a token.
    // The 'auth' passed to successfulAuthentication() is the current authenticated user.
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication auth) throws IOException, ServletException {

        Long now = System.currentTimeMillis();
        String token = Jwts.builder()
            .setSubject(auth.getName()) 
            // Convert to list of strings. 
            // This is important because it affects the way we get them back in the Gateway.
            .claim("authorities", auth.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
            .setIssuedAt(new Date(now))
            .setExpiration(new Date(now + jwtConfig.getExpiration() * 1000))  // in milliseconds
            .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
            .compact();

        // Add token to header
        response.addHeader("Access-Control-Expose-Headers", "authorization");
//      response.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
        response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
    }

    // A (temporary) class just to represent the user credentials
    private static class UserCredentials {
        private String username, password;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }
}

如何为错误响应自定义消息?

1 个答案:

答案 0 :(得分:0)

您可以自定义UsernamePasswordAuthenticationFilter的内部AuthenticationFailureHandler。当onAuthenticationFailure()未能通过身份验证并抛出attemptAuthentication()时,将调用其AuthenticationException

默认实现为SimpleUrlAuthenticationFailureHandler,它仅将响应状态设置为401。您可以简单地扩展它,并将响应消息添加到HttpServletResponse,如下所示:

public MySimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{


    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {


        if (defaultFailureUrl == null) {
               response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
               response.getWriter().write("Your Response Message blablala");
        }else{
            super.onAuthenticationFailure(request,response, exception);
        }
    }

}

当然,您必须设置为在创建AuthenticationFailureHandler之后使用自定义的JwtUsernameAndPasswordAuthenticationFilter

您还需要更改以将AuthenticationException而不是RuntimeException扔到attemptAuthentication()中。