spring security的permitAll()在JWT认证过滤器中不起作用。

huangapple go评论176阅读模式
英文:

spring security permitAll() not working for JWT Authentication filter

问题

问题出在使用了自定义的JWT身份验证过滤器的应用程序上,该过滤器扩展了UsernamePasswordAuthenticationFilter,该过滤器接受用户凭据并返回一个长期有效的JWT。

问题似乎出在permitAll()上,它应该绕过自定义的授权过滤器。然而,在调试模式下,我可以看到首先调用了自定义的JwtAuthorizationFilter而不是自定义的JwtAuthenticationFilter过滤器,这最终导致了403禁止访问的响应。

请注意.antMatchers(HttpMethod.POST, "/login").permitAll()这一行。由于在用户尚未登录时尚未生成JWT,因此应该可以访问/login端点而无需JWT。

以下是您的代码片段:

// JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    // ...
}

// JwtAuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    // ...
}

// SecurityConfiguration.java
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    // ...
}

请求和响应的截图没有完整显示,所以如果您需要关于问题的更多信息,请提供完整的请求和响应内容。

英文:

The issue is with the app uses custom JWT authentication filter which extends UsernamePasswordAuthenticationFilter which accepts user credentials and generates a long-lived JWT in return.

The issue seems to be with permitAll() which should bypass custom Authorization filter.However in debug mode I could see call to custom JwtAuthorizationFilter first instead of custom JwtAuthenticationFilter Filter which eventually results with 403 forbidden Access denied response.

Note the .antMatchers(HttpMethod.POST, "/login").permitAll() line. /login endpoint should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.

Below is my code

> JwtAuthenticationFilter.java

public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
	//
	private AuthenticationManager authenticationManager;

	private final static UrlPathHelper urlPathHelper = new UrlPathHelper();

	public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
		setFilterProcessesUrl("/login");
	}

	/**
	 * Trigger when we issue POST request to login / we also need to pass in
	 * {"username: " username, "password": password} in the request body
	 */
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {

		// Grab credentials and map them to login viewmodel

		LoginViewModel credentials = null;

		try {
			credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Create login token
		UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
				credentials.getUsername(), credentials.getPassword(), new ArrayList<>());

		// Authenciate user
		Authentication auth = authenticationManager.authenticate(authenticationToken);

		return auth;
	}

	@Override
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
		// Grab principal
		UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();

		// Create JWT Token
		String token = JWT.create().withSubject(principal.getUsername())
				.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
				.sign(HMAC512(JwtProperties.SECRET.getBytes()));

		// add token in response
		response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
	}

	@Override
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
		logger.debug("failed authentication while attempting to access "
				+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));

		// Add more descriptive message
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
	}

}

> JwtAuthorizationFilter.java

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private UserRepository userRepository;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
        super(authenticationManager);
        this.userRepository = userRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        //Read the Authorization header, where the JWT token should be
        String header = request.getHeader(JwtProperties.HEADER_STRING);

        //If header does not contain BEARER or is null delegate to Spring impl and exit
        if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }


        // If header is present, try grab user principal from db and perform authorization
        Authentication authentication = getUsernamePasswordAuthentication(request);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // Continue filter execution
        chain.doFilter(request, response);
    }

    private Authentication getUsernamePasswordAuthentication(HttpServletRequest request){
        String token = request.getHeader(JwtProperties.HEADER_STRING)
                .replace(JwtProperties.TOKEN_PREFIX, "");

        if(token !=null){
            //parse the token validate it
            String userName = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
                    .build()
                    .verify(token)
                    .getSubject();
            // Search in the DB if we find the user by token subject(username)
            // If so, then grab user details and create auth token using username, pass, authorities/roles

            if(userName != null){
                User user = userRepository.findByUsername(userName);
                UserPrincipal principal = new UserPrincipal(user);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
                return authenticationToken;
            }
            return null;
        }
        return null;
    }
}

> SecurityConfiguration.java

Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

	private UserPrincipalDetailsService userPrincipalDetailsService;
	private UserRepository userRepository;

	public SecurityConfiguration(UserPrincipalDetailsService userPrincipalDetailsService,
			UserRepository userRepository) {
		this.userPrincipalDetailsService = userPrincipalDetailsService;
		this.userRepository = userRepository;
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) {
		auth.authenticationProvider(authenticationProvider());
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
				// remove csrf state in session because in jwt do not need them
				.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
				.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll()
				.antMatchers("/api/public/management/*").hasRole("MANAGER").antMatchers("/api/public/admin/*")
				.hasRole("ADMIN").anyRequest().authenticated().and()
				// add jwt filters (1. authentication, 2. authorization_)
				.addFilter(new JwtAuthenticationFilter(authenticationManager()))
				.addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository));
		// configure access rules

	}

	@Bean
	DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
		daoAuthenticationProvider.setUserDetailsService((UserDetailsService) this.userPrincipalDetailsService);

		return daoAuthenticationProvider;
	}

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

}

Request,Response

spring security的permitAll()在JWT认证过滤器中不起作用。

Can someone suggest whats wrong here..Appreciate your help..Thanks in advance..!!!

答案1

得分: 1

似乎你的路径是错误的。当你查看你的请求体时,你会发现路径显示为:/login%0A。这看起来你的URL末尾多了一个额外的字符。尝试在Postman中重新编写URL。

英文:

It seems that your path is wrong. When you look at your body you can see that the path shows following: /login%0A. This seems that you have an extra character at the end of your URL. Just try to rewrite the URL in Postman.

答案2

得分: 1

请考虑使用BasicAuthenticationFilter中的shouldNotFilter方法。它继承自OncePerRequestFilter,因此您可以在过滤器类中按如下方式使用:

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
    // 在此处编写代码
}
英文:

please consider to use shouldNotFilter method from BasicAuthenticationFilter. It extends OncePerRequestFilter so you can use it in filtering class as below:

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
       // code here
}

</details>



huangapple
  • 本文由 发表于 2020年10月5日 15:02:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/64203795.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定