Spring Security JWT Пример без пружинной загрузки - PullRequest
0 голосов
/ 25 января 2020

У меня есть приложение Spring 5 MVC / REST. Я использую JPA Hibernate в качестве ORM. Я хочу защитить свое приложение. Все примеры, которые я вижу, основаны на Spring Boot. Я хочу использовать JWT в моем приложении. Я тоже хочу без XML Config.

( Spring MVC - 5.2.0.RELEASE )

Мне нужен пример. Спасибо

1 Ответ

0 голосов
/ 01 февраля 2020

Чтобы защитить приложение Spring REST с помощью Spring Security и JWT, вы можете выполнить следующие шаги

  1. Добавить следующие зависимости в pom. xml

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
  2. Создание JwtAuthenticationEntryPoint для повторной отправки каждого неаутентифицированного запроса

    @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");
    }
    }
    
  3. Создание классов моделей

    public class JwtRequest implements Serializable {
        private static final long serialVersionUID = 5926468583005150707L;
        private String username;
        private String password;
    
        // need default constructor for JSON Parsing
        public JwtRequest() {
        }
    
        public JwtRequest(String username, String password) {
            this.setUsername(username);
            this.setPassword(password);
        }
    // Getters and Setters
    }
    
    public class JwtResponse implements Serializable {
        private static final long serialVersionUID = -8091879091924046844L;
        private final String jwttoken;
    
        public JwtResponse(String jwttoken) {
            this.jwttoken = jwttoken;
        }
    
        public String getToken() {
            return this.jwttoken;
        }
    }
    
  4. Создание Util класс для генерации и проверки токена

    @Component
    public class JwtTokenUtil implements Serializable {
        private static final long serialVersionUID = -2550185165626007488L;
        public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
        @Value("${jwt.secret}")
        private String secret;
    
        // retrieve username from jwt token
        public String getUsernameFromToken(String token) {
            return getClaimFromToken(token, Claims::getSubject);
        }
    
        // retrieve expiration date from jwt token
        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);
        }
    
        // for retrieveing any information from token we will need the secret key
        private Claims getAllClaimsFromToken(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
    
        // check if the token has expired
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
    
        // generate token for user
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            return doGenerateToken(claims, userDetails.getUsername());
        }
    
        // while creating the token -
        // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
        // 2. Sign the JWT using the HS512 algorithm and secret key.
        // 3. According to JWS Compact
        // Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
        // compaction of the JWT to a URL-safe string
        private String doGenerateToken(Map<String, Object> claims, String subject) {
            return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
        // validate token
        public Boolean validateToken(String token, UserDetails userDetails) {
            final String username = getUsernameFromToken(token);
            return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
        }
    }
    
  5. Создание реализаций UserDetailsService и UserRepository для загрузки пользователя по имени пользователя из базы данных

    @Service
    public class JwtUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UserRepository repo;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserEntity user = repo.findByUsername(username);
            if (user != null) {
                return new User(user.getUsername(), user.getPassword(), new ArrayList<>());
            } else {
                throw new UsernameNotFoundException("User not found with username: " + username);
            }
        }
    }
    
  6. Создайте фильтр запроса, чтобы проверить, имеет ли запрос действительный токен JWT. Если у него есть действительный токен JWT, тогда установите Аутентификацию в контексте, чтобы указать, что текущий пользователь аутентифицирован.

    @Component
    public class JwtRequestFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestTokenHeader = request.getHeader("Authorization");
        String username = null;
        String jwtToken = null;
        // JWT Token is in the form "Bearer token". Remove Bearer word and get
        // only the Token
        if (requestTokenHeader != null &amp;&amp; requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                System.out.println("JWT Token has expired");
            }
        } else {
            logger.warn("JWT Token does not begin with Bearer String");
        }
        // Once we get the token validate it.
        if (username != null &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            // if token is valid configure Spring Security to manually set
            // authentication
            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // After setting the Authentication in the context, we specify
                // that the current user is authenticated. So it passes the
                // Spring Security Configurations successfully.
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
    }
    
  7. Создайте контроллер аутентификации для предоставления POST API / authenticate , который принимает имя пользователя и пароль в теле. Используя Spring Authentication Manager, мы аутентифицируем имя пользователя и пароль. Если учетные данные являются действительными, токен JWT создается с использованием JwtTokenUtil и предоставляется клиенту.

        @RestController
        @CrossOrigin
        public class JwtAuthenticationController {
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
        @Autowired
        private JwtUserDetailsService userDetailsService;
    
        @PostMapping("/authenticate")
        public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
            authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
            final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
            final String token = jwtTokenUtil.generateToken(userDetails);
            return ResponseEntity.ok(new JwtResponse(token));
        }
    
        private void authenticate(String username, String password) throws Exception {
            try {
                authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
            } catch (DisabledException e) {
                throw new Exception("USER_DISABLED", e);
            } catch (BadCredentialsException e) {
                throw new Exception("INVALID_CREDENTIALS", e);
            }
        }
        }       
    
  8. Настройка Spring Security

    @Configuration
    @ComponentScan(basePackages = "com.javachinna")
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private UserDetailsService jwtUserDetailsService;
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // configure AuthenticationManager so that it knows from where to load
        // user for matching credentials
        // Use BCryptPasswordEncoder
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // We don't need CSRF for this example
        httpSecurity.csrf().disable()
                // dont authenticate this particular request
                .authorizeRequests().antMatchers("/authenticate").permitAll().
                // all other requests need to be authenticated
                anyRequest().authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // Add a filter to validate the tokens with every request
        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
    }
    
  9. Создание SecurityWebApplicationInitializer для регистрации springSecurityFilterChain

    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    
    }
    
  10. Добавление класса WebSecurityConfig к существующему ApplicationInitializer

    public class SpringWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { WebSecurityConfig.class };
    }
    // ... other overrides ...
    }
    
  11. Добавить секрет ключ к файлу application.properties

    Свойства приложения

    jwt.secret = javachinna

Вот и все. Теперь сгенерируйте JSON Web Token, создав запрос POST с url localhost: 8080 / authenticate и действительными именем пользователя и паролем в теле запроса. Используйте этот токен в заголовке запроса для каждого запроса.

Для получения дополнительной информации см. Статью о защите служб Spring REST с использованием JWT без использования весенней загрузки .

...