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

时间:2018-08-26 17:03:15

标签: 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;
}
}

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

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

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

谢谢!

更新:我发布了其余的类,因为问题可能不仅与这2个有关

2 个答案:

答案 0 :(得分:0)

您没有在PasswordEncoder方法中使用globalUserDetails()。 Spring Security默认情况下采用编码密码。您的代码应类似于。

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
   auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
     .withUser("admin")
         .password("password")
            .roles("Admin");
}
@Bean 
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();

} 您正在使用不需要dataSource,因为您正在使用inMemoryAuthenticatin()

答案 1 :(得分:0)

在Spring Security 5中,如果您使用BaseString,则将无法使用auth.inMemoryAuthentication()BCryptPasswordEncoder。您必须使用自己的StandardPasswordEncoder才能获得用户名和密码。或者,如果您需要测试代码,只需在UserDetailsService方法中返回NoOpPasswordEncoder.getInstance()

SecurityConfig.class

passwordEncoder()

AccountService.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();
            }

        ....

    }

Account.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());
    }

}