Аутентификация jwt для нескольких пользователей при весенней загрузке

У меня есть следующее приложение.

SpringMainApplication.java

@SpringBootApplication
public class SpringMainApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SpringMainApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringMainApplication.class, args);
    }

}

@RestController
class MainController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtil jwtTokenUtil;

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Value("${token.expiration.time}")
    private int tokenExpirationTime;

    @RequestMapping(value = "/service1/access-token", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {

        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
        }
        catch (BadCredentialsException e) {
            throw new Exception("Incorrect password", e);
        }

        final UserDetails userDetails = userDetailsService
            .loadUserByUsername(authenticationRequest.getUsername());

        final String jwt = jwtTokenUtil.generateToken(userDetails);

        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("expiration", String.valueOf(tokenExpirationTime / 1000));

        return ResponseEntity.ok().headers(responseHeaders).body(new AuthenticationResponse(jwt));
    }

    @RequestMapping(value = "/service1/test", consumes = "application/json", method = RequestMethod.GET)
    public ResponseEntity<?> checkServerStatus() throws Exception {
        // SOME CODE
    }

    @RequestMapping(value = "/service2/access-token", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {

        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
        }
        catch (BadCredentialsException e) {
            throw new Exception("Incorrect password", e);
        }

        final UserDetails userDetails = userDetailsService
            .loadUserByUsername(authenticationRequest.getUsername());

        final String jwt = jwtTokenUtil.generateToken(userDetails);

        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set("expiration", String.valueOf(tokenExpirationTime / 1000));

        return ResponseEntity.ok().headers(responseHeaders).body(new AuthenticationResponse(jwt));
    }

    @RequestMapping(value = "/service2/test", consumes = "application/json", method = RequestMethod.GET)
    public ResponseEntity<?> checkServerStatus() throws Exception {
        // SOME CODE
    }

@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService myUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService);
    }

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

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
            .authorizeRequests().antMatchers("/**/service1/access-token").permitAll().
            anyRequest().authenticated().and().
            exceptionHandling().and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

MyUserDetailsService.java

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Value("${username1}")
    private String username;

    @Value("${password1}")
    private String password;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        if (username != null && username.equals(username1)) {
            return new User(username, password1, new ArrayList<>());
        } else {
            throw new UsernameNotFoundException("Username not found: " + username);
        }
    }

}

JwtRequestFilter.java

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

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

        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        StringBuilder sb = new StringBuilder();

        try {
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                jwt = authorizationHeader.substring(7);
                username = jwtUtil.extractUsername(jwt);
            }
        } catch (UnsupportedJwtException e) {
            sb = buildResponseBody(request, e.getMessage());
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(sb.toString());
            return;
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                    .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

JwtUtil.java

@Service
public class JwtUtil {

    @Value("${token.expiration.time}")
    private String tokenExpirationTime;

    private String SECRET_KEY = "some_secret";

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }
    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + Integer.parseInt(tokenExpirationTime)))
            .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
    }

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

Проблема, с которой я сталкиваюсь, заключается в том, что если я сгенерирую токен, вызвав service1/access-token, я все равно смогу вызвать service2/test с помощью jwt, и наоборот. Как я могу запретить пользователю доступ к сервису 2 с действительным токеном сервиса 1? Я считаю, что мне, возможно, придется создать второй класс WebSecurityConfig для обработки service2, но я не уверен, как это сделать, потому что приложение не будет компилироваться, когда я попытаюсь добавить его.


person A.J    schedule 13.01.2021    source источник
comment
У меня недостаточно знаний об этом, так как я все еще изучаю JWT, однако повторное использование AuthenticationManager кажется мне немного, я не знаю, ошибочным.   -  person Fullslack    schedule 14.01.2021
comment
Вы должны использовать \@EnableResourceServer для управления другим токеном (с пользователем) для доступа к другому URL-адресу. И вы можете использовать \@EnableAuthorizationServer для определения вашего сервера аутентификации или стороннего поставщика сервера аутентификации (например, Hydra), в котором разные пользователи имеют разные приоритеты с токеном. ResourceServer проверит токен через AuthenticationServer.   -  person jacky-neo    schedule 14.01.2021
comment
Если вы хотите нести ответственность за имя пользователя/пароль, вам нужна база данных или другое место для их хранения. Хранение пользователей в качестве свойств, особенно паролей, недопустимо, их нужно солить и хэшировать. Но на самом деле проблема в том, что ваш дизайн поддерживает только 1 пользователя, его можно расширить на несколько, но не на многих.   -  person DCTID    schedule 14.01.2021
comment
@DCTID Да, я планирую перенести их в базу данных через несколько недель. Я просто хотел посмотреть, как я могу работать с 2 пользователями в данный момент. Не могли бы вы предложить способ, которым это может работать даже с этим дизайном?   -  person A.J    schedule 14.01.2021
comment
@ jacky-neo Я сейчас читаю об этом, но я не уверен на 100%, как это реализовать в моем случае. Есть случайно совет по этому поводу?   -  person A.J    schedule 14.01.2021
comment
Мой совет основан на аутентификации oauth2. Я думаю, что модификация будет огромной. Вы не должны использовать свой текущий способ создания и проверки токена. Еще один простой способ, я думаю, может быть, вы попробуете '@PreAuthorize(hasAnyAuthority('xxxx')' для каждого URL-адреса запроса. Затем вы просто измените JwtRequestFilter, предоставив разные полномочия в соответствии с ролью пользователя.   -  person jacky-neo    schedule 15.01.2021
comment
Вы уже смотрели этот образец Spring Security: github.com/spring-projects/spring-security-samples/tree/master/ похоже на то, чего вы пытаетесь достичь. Вы можете увидеть некоторые дополнительные детали в этом ответе SO: 64669582" title="spring фильтры безопасности для проверки подлинности и авторизации на основе jwt"> stackoverflow.com/questions/64578081/   -  person jzheaux    schedule 28.01.2021