可以使用任何JWT令牌调用API

时间:2018-03-11 14:54:30

标签: java spring-security jwt

我正在编写一个应用程序,前端用户将通过API调用后端。我已经实现了JWT,用户可以在其中注册,当他们尝试登录时,他们将获得JWT作为响应,然后我可以使用标题中的JWT进行其他调用。如果JWT被排除在标题之外,则呼叫将失败。基础工作按预期工作。 我面临的问题是我可以生成自己的自定义JWT,将其分配给标题并能够成功调用后端。 我在线跟踪了很多教程,发现这个主题非常混乱,而且非常复杂,无法完全掌握。我毫不怀疑这是我在代码中遗漏的一些简单但我无法看到的内容。

这是我到目前为止所做的事情;

AuthenticationFilter

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            User credentials = new     ObjectMapper().readValue(request.getInputStream(), User.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            credentials.getUsername(),
                            credentials.getPassword(),
                            new ArrayList<>()
                    )
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

        String token = Jwts.builder()
                    .setSubject(((org.springframework.security.core.userdetails.User)     authResult.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() +     EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                .compact();
        response.addHeader(HEADER_STRING, TOKEN_PREFIX + token);

    }
}

AuthorizationFilter

public class JWTAuthorizationFilter extends BasicAuthenticationFilter     {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader(HEADER_STRING);

        if(StringUtils.isBlank(header) || ! StringUtils.startsWith(header, TOKEN_PREFIX)){
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);

            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);

        if(StringUtils.isNotBlank(token)){
            String user = Jwts.parser()
                    .setSigningKey(SECRET.getBytes())
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();

            if(StringUtils.isNotBlank(user)){
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }

            return null;
        }

        return null;
    }
}

WebSecurity

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))                    
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

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

    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

正如我所说,我很难找到这个主题,所以我确信我的代码中可能存在简单的错误。如果您能以任何方式帮助或解决我的问题,我们将非常感激

1 个答案:

答案 0 :(得分:1)

这似乎是来自servlet过滤器的关键代码,用于检查JWT:

String token = request.getHeader(HEADER_STRING);

if (StringUtils.isNotBlank(token)){
    String user = Jwts.parser()
        .setSigningKey(SECRET.getBytes())
        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
        .getBody()
        .getSubject();

    if (StringUtils.isNotBlank(user)){
        return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
    }

如果你仔细观察你在做什么,你会发现你的逻辑是从JWT中提取主题。这很正确。但只要主题不是空白,您就可以授权该用户。换句话说,任何用户将被您当前的逻辑授权。相反,你通常会做这样的事情:

if (StringUtils.isNotBlank(user)){
    // check that user against a database/cache
    // if the account is active etc. THEN authorize the user
}

通常,在从JWT中提取主题/用户名后,您将点击数据库/缓存以检查该用户的帐户是否仍处于活动状态。如果没有,那么你将401返回到调用应用程序。 JWT本身并不意味着用户已获得授权,因为在某些时候您可以撤销该用户的令牌,令牌可能会过期等等。