Spring Boot Jwt 返回访问被拒绝

Posted

技术标签:

【中文标题】Spring Boot Jwt 返回访问被拒绝【英文标题】:Spring Boot Jwt returns access denied 【发布时间】:2020-06-13 14:45:41 【问题描述】:

大家好,我对 Java 的 jwt 有疑问。这里是代码。 这是邮递员的返回值


    "timestamp": "2020-02-29T20:53:35.761+0000",
    "status": 403,
    "error": "Forbidden",
    "message": "Access Denied",
    "path": "/login"

TokenManager.java


@Service
public class TokenManager 
    private static final int expiredAt = 10 * 60 * 60 * 1000;
    Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    public String generateToken(String username)


         return Jwts.builder().setSubject(username)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiredAt))
                .signWith(key).compact();
    

    public boolean tokenValidate(String token)
        if(getUserFromToken(token) != null &&  isExpired(token)) 
            return true;
        
        return false;
    

    public String getUserFromToken(String token)
        Claims claims = getClaims(token);
        return claims.getSubject();
    

    public boolean isExpired(String token)
        Claims claims = getClaims(token);
        return claims.getExpiration().after(new Date(System.currentTimeMillis()));
    

    private Claims getClaims(String token) 
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    
 

然后是 JwtTokenFilter.java

@Component
public class JwtTokenFilter extends OncePerRequestFilter 
    @Autowired
    private TokenManager tokenManager;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    @NotNull HttpServletResponse httpServletResponse,
                                    @NotNull FilterChain filterChain) throws ServletException, IOException 


        final String authHeader = httpServletRequest.getHeader("Authorization");

        String username = null;
        String token = null;


        if (authHeader != null && authHeader.contains("Bearer")) 
            token = authHeader.substring(7);
            try 
                username = tokenManager.getUserFromToken(token);
             catch (Exception e) 
                System.out.println(e.getMessage());
            
        

        if (username != null && token != null
                && SecurityContextHolder.getContext().getAuthentication() == null) 
            if (tokenManager.tokenValidate(token)) 
                UsernamePasswordAuthenticationToken upassToken =
                        new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                SecurityContextHolder.getContext().setAuthentication(upassToken);
            
        

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    


And my custom UserDetailService

@Service
public class CustomUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService 

    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        return userRepository.findByUsername(username);
    


这里是 WebSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Autowired
    private JwtTokenFilter tokenFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception 

        http.csrf().disable()
                .authorizeRequests().antMatchers("/signup","/login").permitAll()
                .anyRequest().authenticated();


        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);

    

    @Bean
    public AuthenticationManager getAuthenticationManager() throws Exception 
        return super.authenticationManagerBean();
    


最后一个是我的控制器。我检查了请求正文并打印了它工作正常但 /login 路径返回访问被拒绝的数据。

@RestController
public class UserController 
    @Autowired
    private UserService userService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private TokenManager tokenManager;

    public UserController(UserService userService, AuthenticationManager authenticationManager, TokenManager tokenManager) 
        this.userService = userService;
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
    

    @RequestMapping(value = "/signup", method = RequestMethod.POST)
    public ResponseEntity<User> signup(@RequestBody User user)
        return ResponseEntity.ok(userService.save(user));
    

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ResponseEntity<String> login(@Valid @RequestBody AuthRequest authRequest)
        try
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(),authRequest.getPassword()));
            return ResponseEntity.ok(tokenManager.generateToken(authRequest.getUsername()));
        catch (Exception e)
            throw e;
        
    


当我在登录函数中删除 authenticationManager.authenticate 方法时,它返回一个有效的令牌。但是当我再次添加 authenticationManager 时,它返回访问被拒绝。

【问题讨论】:

【参考方案1】:

其实你没有正确设置AuthenticationManager

在您的代码中,您只是使用了默认身份验证管理器。没关系,因为 Spring 引导安全中提供了一个默认实现,即ProviderManager[ProviderManager][1] 所做的是:

通过AuthenticationProviders 列表迭代身份验证请求。

所以你至少需要一个AuthenticationProvider

有不少AuthenticationProviders,例如:

AnonymousAuthenticationProviderNullAuthenticationProviderDaoAuthenticationProviderLdapAuthenticationProvider

在您的情况下,您正在对数据库进行身份验证,因此选择 DaoAuthenticationProvider

Spring security 有一种非常简单的方法来配置DaoAuthenticationProvider,实际上,当您将userDetailsS​​ervice 设置为AuthenticationManagerBuilder 以配置您的AuthenticationManager 时,它会自动为您创建一个,代码如下:


    @Autowired
    private CustomUserDetailsService userDetailsService;

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

所以您需要做的就是将上面的代码片段添加到您的 WebSecurityConfig

并且还建议使用 PasswordEncoder 而不是将密码存储为纯文本。一个简单的方法是在将用户保存到数据库之前使用 BCryptPasswordEncoder 对密码进行编码...


    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    
    @Bean
    public PasswordEncoder passwordEncoder()
        return new BCryptPasswordEncoder();
    

【讨论】:

以上是关于Spring Boot Jwt 返回访问被拒绝的主要内容,如果未能解决你的问题,请参考以下文章

用户''@'localhost'的访问被拒绝(使用密码:NO)spring-boot

SQLException:在 Spring Boot 中访问被拒绝

mvn spring-boot:run 导致用户'root'@'localhost'的访问被拒绝(使用密码:YES)

Spring Boot:Oauth2:访问被拒绝(用户是匿名的);重定向到身份验证入口点

Spring Boot + Azure Active Directory 签名的 JWT 被拒绝:需要另一个算法,或者找不到匹配的密钥

从 Spring Boot Starter 1.3.5.RELEASE 升级到 1.5.2 RELEASE 时 Spring Security 中的访问被拒绝错误