没有 Spring Boot 的 Spring Security JWT 示例

Posted

技术标签:

【中文标题】没有 Spring Boot 的 Spring Security JWT 示例【英文标题】:Spring Security JWT Example without spring boot 【发布时间】:2020-05-11 10:15:58 【问题描述】:

我有一个 Spring 5 MVC/REST 应用程序。我使用 JPA Hibernate 作为 ORM。 我想保护我的应用程序。我看到的所有示例都是基于 Spring Boot。 我想在我的应用程序中使用 jwt。我也想要没有 XML Config。

(Spring MVC - 5.2.0.RELEASE)

我需要示例。谢谢

【问题讨论】:

这是您要找的吗?:baeldung.com/spring-security-oauth-jwt 感谢亲爱的@R.G,但我想要没有 Spring boot 的示例 @mohammad_soleimani 你是否设法在没有弹簧靴的情况下找到了解决方案。 亲爱的@oOXAam,现在GitHub上有一个例子 【参考方案1】:

要使用 Spring Security 和 JWT 保护 Spring REST 应用程序,您可以按照以下步骤操作

    在 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>
    

    创建 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");
    
    
    

    创建模型类

    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;
        
    
    

    创建用于令牌生成和验证的 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));
        
    
    

    创建 UserDetailsS​​ervice 和 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);
            
        
    
    

    创建请求过滤器以检查请求是否具有有效的 JWT 令牌。如果它具有有效的 JWT Token,则在上下文中设置 Authentication,以指定当前用户已通过身份验证。

    @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);
    
    
    

    创建一个身份验证控制器以公开一个 POST API /authenticate,该 API 在正文中接受用户名和密码。使用 Spring Authentication Manager 我们验证用户名和密码。如果凭据有效,则使用 JwtTokenUtil 创建 JWT 令牌并提供给客户端。

        @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);
            
        
               
    

    配置 Spring 安全

    @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);
    
    
    

    创建SecurityWebApplicationInitializer来注册springSecurityFilterChain

    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer 
    
    
    

    将 WebSecurityConfig 类添加到现有的 ApplicationInitializer

    public class SpringWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer 
    @Override
    protected Class[] getRootConfigClasses() 
        return new Class[]  WebSecurityConfig.class ;
    
    // ... other overrides ...
    
    

    将密钥添加到 application.properties 文件中

    应用属性

    jwt.secret=javachinna

就是这样。现在通过使用 url localhost:8080/authenticate 和请求正文中的有效用户名和密码创建 POST 请求来生成 JSON Web 令牌。在每个请求的请求标头中使用此令牌。

有关详细信息,请参阅article on Securing Spring REST services using JWT without using spring boot。

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review 我已在此处包含了答案的基本部分,并提供了链接以供参考。【参考方案2】:

我编写了一个示例,用于使用 Spring security 5.0.0 none boot 和 JWT,dynamic Role。 我希望它很好

    Spring MVC 5.0.0 Spring 安全 5.0.0 休眠 JPA 5.2.11 我的数据库是 Oracle 数据库

你可以下载它: download from github

【讨论】:

以上是关于没有 Spring Boot 的 Spring Security JWT 示例的主要内容,如果未能解决你的问题,请参考以下文章

带有undertow servlet容器的spring-boot应用程序中的“没有共同的密码套件”错误

SpringBoot :Spring boot 中 Redis 的使用

springboot - SqlSessionFactoryBean falls in circular dependencies by Spring Boot's DataSourceIni

spring boot框架的web.xml文件怎么配置

Spring Boot os.web.servlet.pagenotfound

Spring boot - 无法解析jsp视图