美文网首页spring bootJAVAspring
Spring Boot Security 整合 JWT 实现 无

Spring Boot Security 整合 JWT 实现 无

作者: 程序员果果 | 来源:发表于2019-03-31 17:13 被阅读372次

    简介

    JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念。本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用。

    快速上手

    之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不了解 Spring Security 先移步到 Spring Boot Security 详解

    建表

    DROP TABLE IF EXISTS `user`;
    DROP TABLE IF EXISTS `role`;
    DROP TABLE IF EXISTS `user_role`;
    DROP TABLE IF EXISTS `role_permission`;
    DROP TABLE IF EXISTS `permission`;
    
    CREATE TABLE `user` (
    `id` bigint(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(255) NOT NULL,
    `password` varchar(255) NOT NULL,
    PRIMARY KEY (`id`) 
    );
    CREATE TABLE `role` (
    `id` bigint(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    PRIMARY KEY (`id`) 
    );
    CREATE TABLE `user_role` (
    `user_id` bigint(11) NOT NULL,
    `role_id` bigint(11) NOT NULL
    );
    CREATE TABLE `role_permission` (
    `role_id` bigint(11) NOT NULL,
    `permission_id` bigint(11) NOT NULL
    );
    CREATE TABLE `permission` (
    `id` bigint(11) NOT NULL AUTO_INCREMENT,
    `url` varchar(255) NOT NULL,
    `name` varchar(255) NOT NULL,
    `description` varchar(255) NULL,
    `pid` bigint(11) NOT NULL,
    PRIMARY KEY (`id`) 
    );
    
    INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); 
    INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); 
    INSERT INTO role (id, name) VALUES (1,'USER');
    INSERT INTO role (id, name) VALUES (2,'ADMIN');
    INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/hi','',0);
    INSERT INTO permission (id, url, name, pid) VALUES (2,'/admin/hi','',0);
    INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
    INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
    INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
    INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);
    

    项目结构

    resources
    |___application.yml
    java
    |___com
    | |____gf
    | | |____SpringbootJwtApplication.java
    | | |____config
    | | | |____.DS_Store
    | | | |____SecurityConfig.java
    | | | |____MyFilterSecurityInterceptor.java
    | | | |____MyInvocationSecurityMetadataSourceService.java
    | | | |____MyAccessDecisionManager.java
    | | |____entity
    | | | |____User.java
    | | | |____RolePermisson.java
    | | | |____Role.java
    | | |____mapper
    | | | |____PermissionMapper.java
    | | | |____UserMapper.java
    | | | |____RoleMapper.java
    | | |____utils
    | | | |____JwtTokenUtil.java
    | | |____controller
    | | | |____AuthController.java
    | | |____filter
    | | | |____JwtTokenFilter.java
    | | |____service
    | | | |____impl
    | | | | |____AuthServiceImpl.java
    | | | | |____UserDetailsServiceImpl.java
    | | | |____AuthService.java
    

    关键代码

    pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>
    

    application.yml

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/spring-security-jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    
    

    SecurityConfig

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
            //校验用户
            auth.userDetailsService( userDetailsService ).passwordEncoder( new PasswordEncoder() {
                //对密码进行加密
                @Override
                public String encode(CharSequence charSequence) {
                    System.out.println(charSequence.toString());
                    return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                }
                //对密码进行判断匹配
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                    boolean res = s.equals( encode );
                    return res;
                }
            } );
    
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable()
                    //因为使用JWT,所以不需要HttpSession
                    .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    //OPTIONS请求全部放行
                    .antMatchers( HttpMethod.OPTIONS, "/**").permitAll()
                    //登录接口放行
                    .antMatchers("/auth/login").permitAll()
                    //其他接口全部接受验证
                    .anyRequest().authenticated();
    
            //使用自定义的 Token过滤器 验证请求的Token是否合法
            http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
            http.headers().cacheControl();
        }
    
        @Bean
        public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
            return new JwtTokenFilter();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
    
    }
    

    JwtTokenUtil

    /**
     * JWT 工具类
     */
    @Component
    public class JwtTokenUtil implements Serializable {
    
        private static final String CLAIM_KEY_USERNAME = "sub";
    
        /**
         * 5天(毫秒)
         */
        private static final long EXPIRATION_TIME = 432000000;
        /**
         * JWT密码
         */
        private static final String SECRET = "secret";
    
    
        /**
         * 签发JWT
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>(16);
            claims.put( CLAIM_KEY_USERNAME, userDetails.getUsername() );
    
            return Jwts.builder()
                    .setClaims( claims )
                    .setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME  ) )
                    .signWith( SignatureAlgorithm.HS512, SECRET )
                    .compact();
        }
    
        /**
         * 验证JWT
         */
        public Boolean validateToken(String token, UserDetails userDetails) {
            User user = (User) userDetails;
            String username = getUsernameFromToken( token );
    
            return (username.equals( user.getUsername() ) && !isTokenExpired( token ));
        }
    
        /**
         * 获取token是否过期
         */
        public Boolean isTokenExpired(String token) {
            Date expiration = getExpirationDateFromToken( token );
            return expiration.before( new Date() );
        }
    
        /**
         * 根据token获取username
         */
        public String getUsernameFromToken(String token) {
            String username = getClaimsFromToken( token ).getSubject();
            return username;
        }
    
        /**
         * 获取token的过期时间
         */
        public Date getExpirationDateFromToken(String token) {
            Date expiration = getClaimsFromToken( token ).getExpiration();
            return expiration;
        }
    
        /**
         * 解析JWT
         */
        private Claims getClaimsFromToken(String token) {
            Claims claims = Jwts.parser()
                    .setSigningKey( SECRET )
                    .parseClaimsJws( token )
                    .getBody();
            return claims;
        }
    
    
    
    }
    

    JwtTokenFilter

    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        /**
         * 存放Token的Header Key
         */
        public static final String HEADER_STRING = "Authorization";
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
            String token = request.getHeader( HEADER_STRING );
            if (null != token) {
                String username = jwtTokenUtil.getUsernameFromToken(token);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(token, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                                request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            chain.doFilter(request, response);
        }
        
    }
    

    AuthServiceImpl

    @Service
    public class AuthServiceImpl implements AuthService {
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
    
        @Override
        public String login(String username, String password) {
            UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
            Authentication authentication = authenticationManager.authenticate(upToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            UserDetails userDetails = userDetailsService.loadUserByUsername( username );
            String token = jwtTokenUtil.generateToken(userDetails);
            return token;
        }
    
    
    
    }
    

    关键代码就是这些,其他类代码参照后面提供的源码地址。

    验证

    登录,获取token

    curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login
    

    返回

    eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ
    

    不带token访问资源

    curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
    

    返回,拒绝访问

    {
        "timestamp": "2019-03-31T08:50:55.894+0000",
        "status": 403,
        "error": "Forbidden",
        "message": "Access Denied",
        "path": "/auth/login"
    }
    

    携带token访问资源

    curl -X POST -H "Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
    

    返回正确

    hi zhangsan , you have 'admin' role
    

    源码

    https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-jwt

    相关文章

      网友评论

        本文标题:Spring Boot Security 整合 JWT 实现 无

        本文链接:https://www.haomeiwen.com/subject/xujubqtx.html