SpringBoot集成JWT 实现接口权限认证

 

JWT介绍

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点的单点登录(`SSO`)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息, 以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

优点

  • 体积小、传输快
  • 支持跨域授权,因为跨域无法共享cookie
  • 分布式系统中,很好地解决了单点登录问题

缺点

因为JWT是无状态的,因此服务端无法控制已经生成的Token失效,是不可控的

使用场景

1. 认证,这是比较常见的使用场景,只要用户登录过一次系统,之后的请求都会包含签名出来的token,通过token也可以用来实现单点登录。
2. 交换信息,通过使用密钥对来安全的传送信息,可以知道发送者是谁、放置消息被篡改。


springboot集成JWT过程(注意: 使用了数据库, 先建表)


项目克隆

项目名称 springboot-jwt
地址: https://gitee.com/minili/springboot-demo.git
如果觉得该项目对你有帮助或者有疑问的话, 欢迎加星, 评论

添加表

一个是管理员表, 一个是存放token表
在项目下的db文件夹
SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `manager`;
CREATE TABLE `manager` (
  `managerId` int(5) unsigned NOT NULL AUTO_INCREMENT COMMENT '管理员id',
  `managerName` varchar(50) NOT NULL,
  `nickName` varchar(50) DEFAULT NULL,
  `password` varchar(50) NOT NULL,
  `managerLevelId` int(2) NOT NULL,
  PRIMARY KEY (`managerId`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';

INSERT INTO `manager` VALUES ('1', 'admin', 'admin', '4297f44b13955235245b2497399d7a93', '1');
INSERT INTO `manager` VALUES ('2', 'cscscs', 'cscscs', '4297f44b13955235245b2497399d7a93', '1');

DROP TABLE IF EXISTS `managertoken`;
CREATE TABLE `managertoken` (
  `managerId` int(20) NOT NULL,
  `token` varchar(50) NOT NULL,
  `expireTime` varchar(15) DEFAULT NULL COMMENT '过期时间yyyyMMddHHmmss',
  `updateTime` varchar(15) DEFAULT NULL COMMENT '更新时间yyyyMMddHHmmss',
  PRIMARY KEY (`managerId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

POM.XML

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycom</groupId>
    <artifactId>funfast</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>funfast</name>
    <description>project for Spring Boot JWT</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <mysql-connector>5.1.38</mysql-connector>
        <mybatis-plus-boot-starter.version>2.1.9</mybatis-plus-boot-starter.version>
        <druid.version>1.1.10</druid.version>
        <fastjson.version>1.2.39</fastjson.version>
        <jwt.version>0.7.0</jwt.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Spring Boot web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- log4j2 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- Spring Boot Test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot JDBC 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- MySQL 连接驱动 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector}</version>
        </dependency>
        <!-- druid 连接池 依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- shiro 权限控制 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <!-- shiro ehcache (shiro缓存)-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>

        <!-- fastjson 依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

修改application.yml文件 

# server
server:
   tomcat:
        uri-encoding: UTF-8
        max-threads: 1000
        min-spare-threads: 30
   port: 8087
   servlet:
        context-path: /

spring:
    # 环境 dev|prod
    profiles:
        active: dev

    servlet:
        multipart:
            max-file-size: 100MB
            max-request-size: 100MB
            enabled: true

    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      druid:
        url: jdbc:mysql://127.0.0.1:3306/fun-fast?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
        username: root
        password: 123123

        initial-size: 10
        max-active: 100
        min-idle: 10
        max-wait: 60000
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: SELECT 1
        test-while-idle: true
        test-on-borrow: true
        test-on-return: false
        stat-view-servlet:
          enabled: true
          url-pattern: /druid/*
          login-username: admin
          login-password: 123123
        filter:
          stat:
            log-slow-sql: true
            slow-sql-millis: 1000
            merge-sql: false
          wall:
            config:
              multi-statement-allow: true

主体有5个文件需要添加,分别是shiroConfig、OAuth2Filer配置、OAuth2Realm、OAuth2Token、TokenGenerator

 1. ShiroConfig配置

/**
 * Shiro配置
 */
@Configuration
public class ShiroConfig {

    @Bean("sessionManager")
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdCookieEnabled(true);
        return sessionManager;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(oAuth2Realm);
        securityManager.setSessionManager(sessionManager);

        return securityManager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        //oauth过滤
        Map<String, Filter> filters = new HashMap<>();
        filters.put("oauth2", new OAuth2Filter());
        shiroFilter.setFilters(filters);

        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/druid/**", "anon");
        filterMap.put("/app/**", "anon");
        filterMap.put("/login", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter;
    }

    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

2. OAuth2Filer配置

这个里面可以配置权限过滤的规则

 OAuth2Filer

3. OAuth2Realm配置

这个里面可以设置角色、权限和认证信息

 OAuth2Realm

4. OAuth2Token设置

 View Code

5. TokenGenerator, 生成token

 View Code

 

Controller

@RestController
public class WebController {

    private static final Logger LOGGER = LogManager.getLogger(WebController.class);

    @Autowired
    private ManagerService managerService;

    @RequestMapping("/login")
    public JSONObject login(@RequestParam("username") String username,
                            @RequestParam("password") String password) {
        JSONObject json = new JSONObject();
        json.put("result", false);
        json.put("msg", "账号或密码不正确");

        // 用户信息
        ManagerInfo managerInfo = managerService.getManagerInfo(username);
        // 账号不存在、密码错误
        if (managerInfo == null || !managerInfo.getPassword().equals(password)) {
            return json;
        }

        ManagerToken managerToken = managerService.saveToken(managerInfo.managerId);
        json.put("token", managerToken.token);
        json.put("result", true);
        json.put("msg", "登陆成功");

        return json;
    }

    /**
     * 必须带token请求, 否则返回401
     */
    @GetMapping("/article")
    public BaseResponse article() {
        return new BaseResponse(true, "article: You are already logged in", null);
    }

    /**
     * 不必带token也能请求到内容, 因为在shiro中配置了过滤规则
     */
    @GetMapping("/app/article")
    public BaseResponse appArticle() {
        return new BaseResponse(true, "appArticle: You are already logged in", null);
    }

    /**
     * 需要是超级管理员的token才能查看,
     */
    @GetMapping("/require_role")
    @RequiresRoles("admin")
    public BaseResponse requireRole() {
        return new BaseResponse(true, "You are visiting require_role", null);
    }

    /**
     * 需要有update权限才能访问
     */
    @GetMapping("/require_permission")
    // @RequiresPermissions(logical = Logical.AND, value = {"view", "edit"})
    @RequiresPermissions(logical = Logical.AND, value = {"update"})
    public BaseResponse requirePermission() {
        return new BaseResponse(true, "You are visiting permission require update", null);
    }

}
 

 

Service

 service

 

这里省略了一些基础的实体类、工具类,详见代码


登陆测试

先登陆获取到token(localhost:8087/login?username=cscscs&password=4297f44b13955235245b2497399d7a93)
这里测试的用户有两个一个admin(超级管理员), 一个是cscscs

 

测试需要认证的接口

 1.不带token访问

 

 2.带token访问

注意是headers中添加参数token

 

测试不需要认证的接口

在OAuth2Filter中我对,/app 路径下的接口不需要认证

测试角色认证的接口

/require_role, 这个接口需要admin角色才能访问,在OAuth2Realm中我设置了admin用户为超级管理员角色, cscscs用户为test角色

1. cscscs用户访问(选择cscscs用户的token)

2.admin用户访问(选择admin用户的token)

测试权限认证的接口

平时我们可以设置管理员是否有删除,更新记录的权限

/require_permission在控制器中设置了只有拥有update权限的用户才能访问, 在OAuth2Realm中我给了admin用户update的权限, 给了cscscs用户view的权限

1.使用cscscs用户的token访问

 

2. 使用admin用户访问

 

结语

1. 希望能给自己帮助、也给别人帮助,有任何疑问或者意见,在下方留言哦

2, 有很多不足可以改进, 如缓存啊, 更准确的权限设置啊, 但他可以帮你构建一个完整可用的JWT

   之前看了网上的例子, 理论很好, 例子却没跑通, aaaaa,,,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值