使用 JWT 的 Spring Security 总是返回 401 未经授权

标签 spring spring-security jwt

1

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Resource(name = "userService")
private UserDetailsService userDetailsService;

@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
    /*auth.jdbcAuthentication()
    .dataSource(dataSource)
    .usersByUsernameQuery("select login as principal, mot_de_passe as credentials, flag_compte_actif as enabled from utilisateur where login = ?")
    .authoritiesByUsernameQuery("SELECT utilisateur.login as principal, profil.designation as role FROM utilisateur INNER JOIN user_profil ON utilisateur.id_user = user_profil.iduserpk INNER JOIN profil ON user_profil.idprofilpk = profil.id_profil WHERE utilisateur.login = ? ")
    .rolePrefix("ROLE_");

    auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());*/
    auth.inMemoryAuthentication()
        .withUser("admin")
            .password("password")
                .roles("Administrateur");
}

@Bean
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
    return new JwtAuthenticationFilter();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    System.out.println("Akal configure method begin");
    //http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);

    http.cors().and()
    .csrf().disable().
            authorizeRequests()
            .antMatchers("/token/generate").permitAll()
            .anyRequest().authenticated()
            .and().formLogin().disable()
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    System.out.println("Akal configure method");
    http
            .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);


}

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

    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

}

2
@RestController
@CrossOrigin("*")
public class AuthenticationController {

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private JwtTokenUtil jwtTokenUtil;

  @Autowired
  private UtilisateurRepository userRepo;

  @PostMapping(value = "/token/generate")
  public ResponseEntity<?> register(@RequestBody LoginUser loginUser) throws AuthenticationException {
     System.out.println("We're in man!");
     final Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    loginUser.getUsername(),
                    loginUser.getPassword()
             )
     );
     System.out.println("(Username, Password): (" + loginUser.getUsername() + ", " + loginUser.getPassword() + ")");
     SecurityContextHolder.getContext().setAuthentication(authentication);
     final Utilisateur user = userRepo.findByLogin(loginUser.getUsername());
     final String token = jwtTokenUtil.generateToken(user);
     System.out.println("Token Controller Access=> Token Generated: " + token);
     return ResponseEntity.ok(new AuthToken(token));
 }

}

3
public class AuthToken {

private String token;

public AuthToken(){

}

public AuthToken(String token){
    this.token = token;
}

public String getToken() {
    return token;
}

public void setToken(String token) {
    this.token = token;
}

}

4
public class CorsFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    System.out.println("Filtering on...........................................................");
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Credentials", "true");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
    //response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers");

    chain.doFilter(req, res);
}

public void init(FilterConfig filterConfig) {}

public void destroy() {}

}

5
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

@Override
public void commence(HttpServletRequest request,
                     HttpServletResponse response,
                     AuthenticationException authException) throws IOException {

    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}

6
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
    String header = req.getHeader("Authorization");
    String username = null;
    String authToken = null;
    if (header != null && header.startsWith("Bearer ")) {
        authToken = header.replace("Bearer ","");
        try {
            username = jwtTokenUtil.getUsernameFromToken(authToken);
        } catch (IllegalArgumentException e) {
            logger.error("an error occured during getting username from token", e);
        } catch (ExpiredJwtException e) {
            logger.warn("the token is expired and not valid anymore", e);
        } catch(SignatureException e){
            logger.error("Authentication Failed. Username or Password not valid.");
        }
    } else {
        logger.warn("couldn't find bearer string, will ignore the header");
    }
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (jwtTokenUtil.validateToken(authToken, userDetails)) {
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
            logger.info("authenticated user " + username + ", setting security context");
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    }

    chain.doFilter(req, res);
}
}

7
@Component
public class JwtTokenUtil implements Serializable {

static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "secret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";


public String getUsernameFromToken(String token) {
    return getClaimFromToken(token, Claims::getSubject);
}

public Date getExpirationDateFromToken(String token) {
    return getClaimFromToken(token, Claims::getExpiration);
}

public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = getAllClaimsFromToken(token);
    return claimsResolver.apply(claims);
}

private Claims getAllClaimsFromToken(String token) {
    return Jwts.parser()
            .setSigningKey(SECRET)
            .parseClaimsJws(token)
            .getBody();
}

private Boolean isTokenExpired(String token) {
    final Date expiration = getExpirationDateFromToken(token);
    return expiration.before(new Date());
}

public String generateToken(Utilisateur user) {
    return doGenerateToken(user.getLogin());
}

private String doGenerateToken(String subject) {

    Claims claims = Jwts.claims().setSubject(subject);
    claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_Administrateur")));

    return Jwts.builder()
            .setClaims(claims)
            .setIssuer("http://devglan.com")
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
            .signWith(SignatureAlgorithm.HS256, SECRET)
            .compact();
}

public Boolean validateToken(String token, UserDetails userDetails) {
    final String username = getUsernameFromToken(token);
    return (
          username.equals(userDetails.getUsername())
                && !isTokenExpired(token));
}

}

8
public class LoginUser {

private String username;
private String 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;
}
}

我只贴了这2个类,因为老实说我有8个配置类,读起来会很痛苦!它也是自定义的 JWT 代码,但如果有必要全部发布,请告诉我。

除此之外,我就是无法确定问题所在! Spring 控制台没有显示任何错误,当我尝试从 Postman 请求时,结果如下:
result

当我从浏览器运行请求时,它没有说 401,它只是说错误的凭据,即使它们是正确的,我也尝试了数十个用户以确保

谢谢!

更新:我发布了其余的类(class),因为问题可能与这两个无关

最佳答案

在 Spring Security 5 中,如果您使用的是 auth.inMemoryAuthentication() ,您将无法使用 BCryptPasswordEncoderStandardPasswordEncoder .您必须使用自己的 UserDetailsService以获取用户和密码。或者,如果您需要测试您的代码,只需返回 NoOpPasswordEncoder.getInstance()在您的 passwordEncoder()方法

SecurityConfig.class

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

            @Autowired
            private AccountService accountService; //your own implementation of UserDetailsService

        ....

            @Override
            public void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth
                .eraseCredentials(true)
                .userDetailsService(accountService)
                .passwordEncoder(passwordEncoder());
            }

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

        ....

    }

AccountService.class
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AccountService implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository; //Your database repository

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostConstruct  
    protected void initialize() {
        save(new Account("user", "demo", "ROLE_USER"));
        save(new Account("admin", "admin", "ROLE_ADMIN"));
    }

    @Transactional
    public Account save(Account account) {
        account.setPassword(passwordEncoder.encode(account.getPassword()));
        accountRepository.save(account);
        return account;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findOneByEmail(username);
        if(account == null) {
            throw new UsernameNotFoundException("user not found");
        }
        return createUser(account);
    }

    public void signin(Account account) {
        SecurityContextHolder.getContext().setAuthentication(authenticate(account));
    }

    private Authentication authenticate(Account account) {
        return new UsernamePasswordAuthenticationToken(createUser(account), null, Collections.singleton(createAuthority(account)));     
    }

    private User createUser(Account account) {
        return new User(account.getEmail(), account.getPassword(), Collections.singleton(createAuthority(account)));
    }

    private GrantedAuthority createAuthority(Account account) {
        return new SimpleGrantedAuthority(account.getRole());
    }

}

Account.class
@SuppressWarnings("serial")
@Entity
@Table(name = "account")
public class Account implements java.io.Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @Column(unique = true)
    private String email;

    @JsonIgnore
    private String password;

    private String role = "ROLE_USER";

    private Instant created;

    protected Account() {

    }

    public Account(String email, String password, String role) {
        this.email = email;
        this.password = password;
        this.role = role;
        this.created = Instant.now();
    }

    public Long getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public Instant getCreated() {
        return created;
    }
}

关于使用 JWT 的 Spring Security 总是返回 401 未经授权,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52028457/

相关文章:

spring - Dispatcher-servlet 和 spring-servlet 之间的区别

java - 我不想从 axboot 记录 jpa 查询

java - 使用 AngularJS 和 SPRING 插入数据库

java - Spring 安全: log in using roles from database

json - 如何向每个 header 添加 json 网络 token ?

java - Spring JPA、Hibernate中哪个类负责扫描@Entity注解?

java - Spring Security 加密字符串 - Go 中解密失败

spring-mvc - Spring MockMvc 重定向不工作

Django rest framework JWT,删除jwt token

javascript - 始终传递 http 身份验证 header javascript