zoukankan      html  css  js  c++  java
  • Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介

    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 系列精选

    Spring Boot 自定义 starter
    Spring Boot 整合 mybatis-plus
    Spring Boot 整合 spring cache
    Spring Boot 整合 rabbitmq
    Spring Boot 整合 elasticsearch
    Spring Boot 整合 docker
    Spring Boot 整合 elk
    Spring Boot Admin 2.0 详解
    Spring Boot 整合 apollo
    Spring Boot Security 详解
    Spring Boot Security 整合 OAuth2 设计安全API接口服务
    Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    SpringCloud 系列精选

    Spring Cloud Gateway 入门
    Spring Cloud Gateway 之 Predict
    Spring Cloud Gateway 之 Filter
    Spring Cloud Gateway 之 限流
    Spring Cloud Gateway 之 服务注册与发现

    Dubbo 系列精选

    Dubbo 搭建管理控制台
    Dubbo 创建 提供者 消费者
    Dubbo 配置
    Dubbo 高可用

    Docker 系列精选

    服务 Docker 化
    Docker 私有仓库搭建
    DockerSwarm 集群环境搭建
    DockerSwarm 微服务部署




    欢迎扫码或微信搜索公众号《程序员果果》关注我,关注有惊喜~

  • 相关阅读:
    Nginx ab压力测试
    Golang入门教程(二)Ubuntu16.04下安装golang(实例:Golang 定时任务管理器)
    史上最全的常用学术网站
    Quant Reading List Derivative Pricing
    Magic Quadrant for Security Information and Event Management
    5 Top Books for Acing a Quantitative Analyst Interview
    5 Important But Not So Common Books A Quant Should Read Before Applying for a Job
    Top 5 Essential Beginner C++ Books for Financial Engineers
    国密算法概述 SM1、SM2、SM3、SM4、SM7、SM9、ZUC
    Openstack Barbican部署选项如何保护您的云[Openstack]
  • 原文地址:https://www.cnblogs.com/huanchupkblog/p/10634510.html
Copyright © 2011-2022 走看看