600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > SpringBoot集成JWT 实现接口权限认证

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

时间:2022-09-27 23:21:02

相关推荐

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

地址: /minili/springboot-demo.git

如果觉得该项目对你有帮助或者有疑问的话, 欢迎加星, 评论

添加表

一个是管理员表, 一个是存放token表

在项目下的db文件夹

1 SET FOREIGN_KEY_CHECKS=0; 2 3 DROP TABLE IF EXISTS `manager`; 4 CREATE TABLE `manager` ( 5 `managerId` int(5) unsigned NOT NULL AUTO_INCREMENT COMMENT '管理员id', 6 `managerName` varchar(50) NOT NULL, 7 `nickName` varchar(50) DEFAULT NULL, 8 `password` varchar(50) NOT NULL, 9 `managerLevelId` int(2) NOT NULL,10 PRIMARY KEY (`managerId`)11 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';12 13 INSERT INTO `manager` VALUES ('1', 'admin', 'admin', '4297f44b13955235245b2497399d7a93', '1');14 INSERT INTO `manager` VALUES ('2', 'cscscs', 'cscscs', '4297f44b13955235245b2497399d7a93', '1');15 16 DROP TABLE IF EXISTS `managertoken`;17 CREATE TABLE `managertoken` (18 `managerId` int(20) NOT NULL,19 `token` varchar(50) NOT NULL,20 `expireTime` varchar(15) DEFAULT NULL COMMENT '过期时间yyyyMMddHHmmss',21 `updateTime` varchar(15) DEFAULT NULL COMMENT '更新时间yyyyMMddHHmmss',22 PRIMARY KEY (`managerId`)23 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

POM.XML

1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance" 3xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"> 4<modelVersion>4.0.0</modelVersion> 5<groupId>com.mycom</groupId> 6<artifactId>funfast</artifactId> 7<version>0.0.1-SNAPSHOT</version> 8<packaging>jar</packaging> 9 10<name>funfast</name> 11<description>project for Spring Boot JWT</description> 12 13<parent> 14 <groupId>org.springframework.boot</groupId> 15 <artifactId>spring-boot-starter-parent</artifactId> 16 <version>2.0.1.RELEASE</version> 17 <relativePath/> 18</parent> 19 20<properties> 21 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 22 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 23 <java.version>1.8</java.version> 24 <mysql-connector>5.1.38</mysql-connector> 25 <mybatis-plus-boot-starter.version>2.1.9</mybatis-plus-boot-starter.version> 26 <druid.version>1.1.10</druid.version> 27 <fastjson.version>1.2.39</fastjson.version> 28 <jwt.version>0.7.0</jwt.version> 29</properties> 30 31<dependencies> 32 <dependency> 33 <groupId>org.springframework.boot</groupId> 34 <artifactId>spring-boot-starter</artifactId> 35 <exclusions> 36 <exclusion> 37 <groupId>org.springframework.boot</groupId> 38 <artifactId>spring-boot-starter-logging</artifactId> 39 </exclusion> 40 </exclusions> 41 </dependency> 42 43 <!-- Spring Boot web依赖 --> 44 <dependency> 45 <groupId>org.springframework.boot</groupId> 46 <artifactId>spring-boot-starter-web</artifactId> 47 </dependency> 48 <!-- log4j2 依赖 --> 49 <dependency> 50 <groupId>org.springframework.boot</groupId> 51 <artifactId>spring-boot-starter-log4j2</artifactId> 52 </dependency> 53 <!-- Spring Boot Test 依赖 --> 54 <dependency> 55 <groupId>org.springframework.boot</groupId> 56 <artifactId>spring-boot-starter-test</artifactId> 57 <scope>test</scope> 58 </dependency> 59 <dependency> 60 <groupId>org.springframework.boot</groupId> 61 <artifactId>spring-boot-configuration-processor</artifactId> 62 <optional>true</optional> 63 </dependency> 64 65 <!-- Spring Boot JDBC 依赖 --> 66 <dependency> 67 <groupId>org.springframework.boot</groupId> 68 <artifactId>spring-boot-starter-jdbc</artifactId> 69 </dependency> 70 71 <!-- MySQL 连接驱动 依赖 --> 72 <dependency> 73 <groupId>mysql</groupId> 74 <artifactId>mysql-connector-java</artifactId> 75 <version>${mysql-connector}</version> 76 </dependency> 77 <!-- druid 连接池 依赖 --> 78 <dependency> 79 <groupId>com.alibaba</groupId> 80 <artifactId>druid-spring-boot-starter</artifactId> 81 <version>${druid.version}</version> 82 </dependency> 83 84 <!-- shiro 权限控制 --> 85 <dependency> 86 <groupId>org.apache.shiro</groupId> 87 <artifactId>shiro-spring</artifactId> 88 <version>1.4.0</version> 89 </dependency> 90 91 <!-- shiro ehcache (shiro缓存)--> 92 <dependency> 93 <groupId>org.apache.shiro</groupId> 94 <artifactId>shiro-ehcache</artifactId> 95 <version>1.4.0</version> 96 <exclusions> 97 <exclusion> 98 <artifactId>slf4j-api</artifactId> 99 <groupId>org.slf4j</groupId>100 </exclusion>101 </exclusions>102 </dependency>103 104 <!-- jwt -->105 <dependency>106 <groupId>io.jsonwebtoken</groupId>107 <artifactId>jjwt</artifactId>108 <version>${jwt.version}</version>109 </dependency>110 111 <!-- fastjson 依赖 -->112 <dependency>113 <groupId>com.alibaba</groupId>114 <artifactId>fastjson</artifactId>115 <version>${fastjson.version}</version>116 </dependency>117 118</dependencies>119 120<build>121 <plugins>122 <plugin>123 <groupId>org.springframework.boot</groupId>124 <artifactId>spring-boot-maven-plugin</artifactId>125 </plugin>126 </plugins>127</build>128 129 </project>

pom.xml

修改application.yml文件

1 # server 2 server: 3 tomcat: 4 uri-encoding: UTF-8 5 max-threads: 1000 6 min-spare-threads: 30 7 port: 8087 8 servlet: 9 context-path: /10 11 spring:12# 环境 dev|prod13profiles:14 active: dev15 16servlet:17 multipart:18 max-file-size: 100MB19 max-request-size: 100MB20 enabled: true21 22datasource:23 type: com.alibaba.druid.pool.DruidDataSource24 driver-class-name: com.mysql.jdbc.Driver25 druid:26 url: jdbc:mysql://127.0.0.1:3306/fun-fast?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull27 username: root28 password: 12312329 30 initial-size: 1031 max-active: 10032 min-idle: 1033 max-wait: 6000034 pool-prepared-statements: true35 max-pool-prepared-statement-per-connection-size: 2036 time-between-eviction-runs-millis: 6000037 min-evictable-idle-time-millis: 30000038 validation-query: SELECT 139 test-while-idle: true40 test-on-borrow: true41 test-on-return: false42 stat-view-servlet:43 enabled: true44 url-pattern: /druid/*45 login-username: admin46 login-password: 12312347 filter:48 stat:49 log-slow-sql: true50 slow-sql-millis: 100051 merge-sql: false52 wall:53 config:54multi-statement-allow: true

application.yml

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

1. ShiroConfig配置

1 /** 2 * Shiro配置 3 */ 4 @Configuration 5 public class ShiroConfig { 6 7@Bean("sessionManager") 8public SessionManager sessionManager(){ 9 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();10 sessionManager.setSessionValidationSchedulerEnabled(true);11 sessionManager.setSessionIdCookieEnabled(true);12 return sessionManager;13}14 15@Bean("securityManager")16public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {17 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();18 securityManager.setRealm(oAuth2Realm);19 securityManager.setSessionManager(sessionManager);20 21 return securityManager;22}23 24@Bean("shiroFilter")25public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {26 ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();27 shiroFilter.setSecurityManager(securityManager);28 29 //oauth过滤30 Map<String, Filter> filters = new HashMap<>();31 filters.put("oauth2", new OAuth2Filter());32 shiroFilter.setFilters(filters);33 34 Map<String, String> filterMap = new LinkedHashMap<>();35 filterMap.put("/druid/**", "anon");36 filterMap.put("/app/**", "anon");37 filterMap.put("/login", "anon");38 filterMap.put("/**", "oauth2");39 shiroFilter.setFilterChainDefinitionMap(filterMap);40 41 return shiroFilter;42}43 44@Bean("lifecycleBeanPostProcessor")45public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {46 return new LifecycleBeanPostProcessor();47}48 49@Bean50public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {51 DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();52 proxyCreator.setProxyTargetClass(true);53 return proxyCreator;54}55 56@Bean57public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {58 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();59 advisor.setSecurityManager(securityManager);60 return advisor;61}62 63 }

2. OAuth2Filer配置

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

1 /** 2 * oauth2过滤器 3 */ 4 public class OAuth2Filter extends AuthenticatingFilter { 5 6@Override 7protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { 8 //获取请求token 9 String token = getRequestToken((HttpServletRequest) request);10 11 if(StringUtil.isBlank(token)){12 return null;13 }14 15 return new OAuth2Token(token);16}17 18@Override19protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {20 if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){21 return true;22 }23 24 return false;25}26 27@Override28protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {29 //获取请求token,如果token不存在,直接返回40130 HttpServletRequest httpServletRequest = (HttpServletRequest) request;31 String token = getRequestToken((HttpServletRequest) request);32 if(StringUtil.isBlank(token)){33 HttpServletResponse httpResponse = (HttpServletResponse) response;34 httpResponse.setHeader("Access-Control-Allow-Credentials", "true");35 httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));36 37 JSONObject json = new JSONObject();38 json.put("code", "401");39 json.put("msg", "invalid token");40 41 httpResponse.getWriter().print(json);42 43 return false;44 }45 46 return executeLogin(request, response);47}48 49@Override50protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {51 HttpServletResponse httpResponse = (HttpServletResponse) response;52 HttpServletRequest httpServletRequest = (HttpServletRequest) request;53 httpResponse.setContentType("application/json;charset=utf-8");54 httpResponse.setHeader("Access-Control-Allow-Credentials", "true");55 httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));56 try {57 //处理登录失败的异常58 Throwable throwable = e.getCause() == null ? e : e.getCause();59 60 JSONObject json = new JSONObject();61 json.put("code", "401");62 json.put("msg", throwable.getMessage());63 64 httpResponse.getWriter().print(json);65 } catch (IOException e1) {66 67 }68 69 return false;70}71 72/**73* 获取请求的token74*/75private String getRequestToken(HttpServletRequest httpRequest){76 //从header中获取token77 String token = httpRequest.getHeader("token");78 79 //如果header中不存在token,则从参数中获取token80 if(StringUtil.isBlank(token)){81 token = httpRequest.getParameter("token");82 }83 84 return token;85}86 }

OAuth2Filer

3. OAuth2Realm配置

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

1 /** 2 * 认证 3 */ 4 @Component 5 public class OAuth2Realm extends AuthorizingRealm { 6@Autowired 7private ManagerService managerService; 8 9@Override10public boolean supports(AuthenticationToken token) {11 return token instanceof OAuth2Token;12}13 14/**15* 授权(验证权限时调用, 控制role 和 permissins时使用)16*/17@Override18protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {19 ManagerInfo manager = (ManagerInfo)principals.getPrimaryPrincipal();20 Integer managerId = manager.getManagerId();21 22 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();23 24 // 模拟权限和角色25 Set<String> permsSet = new HashSet<>();26 Set<String> roles = new HashSet<>();27 if (managerId == 1) {28 // 超级管理员-权限29 permsSet.add("delete");30 permsSet.add("update");31 permsSet.add("view");32 33 roles.add("admin");34 } else {35 // 普通管理员-权限36 permsSet.add("view");37 38 roles.add("test");39 }40 41 info.setStringPermissions(permsSet);42 info.setRoles(roles);43 44 return info;45}46 47/**48* 认证(登录时调用)49*/50@Override51protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {52 String accessToken = (String) token.getPrincipal();53 54 //根据accessToken,查询用户信息55 ManagerToken managerToken = managerService.queryByToken(accessToken);56 //token失效57 SimpleDateFormat sm = new SimpleDateFormat("yyyyMMddHHmmss");58 Date expireTime;59 boolean flag = true;60 try {61 expireTime= sm.parse(managerToken.getExpireTime());62 flag = managerToken == null || expireTime.getTime() < System.currentTimeMillis();63 } catch (ParseException e) {64 e.printStackTrace();65 }66 67 if(flag){68 throw new IncorrectCredentialsException("token失效,请重新登录");69 }70 71 //查询用户信息72 ManagerInfo managerInfo = managerService.getManagerInfo(managerToken.managerId);73 //账号锁定74 // if(managerInfo.getStatus() == 0){75 //throw new LockedAccountException("账号已被锁定,请联系管理员");76 // }77 78 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(managerInfo, accessToken, getName());79 80 return info;81}82 }

OAuth2Realm

4. OAuth2Token设置

1 /** 2 * token 3 */ 4 public class OAuth2Token implements AuthenticationToken { 5private String token; 6 7public OAuth2Token(String token){ 8 this.token = token; 9}10 11@Override12public String getPrincipal() {13 return token;14}15 16@Override17public Object getCredentials() {18 return token;19}20 }

View Code

5.TokenGenerator, 生成token

1 /** 2 * 生成token 3 */ 4 public class TokenGenerator { 5 6public static String generateValue() { 7 return generateValue(UUID.randomUUID().toString()); 8} 9 10private static final char[] hexCode = "0123456789abcdef".toCharArray();11 12public static String toHexString(byte[] data) {13 if (data == null) {14 return null;15 }16 StringBuilder r = new StringBuilder(data.length * 2);17 for (byte b : data) {18 r.append(hexCode[(b >> 4) & 0xF]);19 r.append(hexCode[(b & 0xF)]);20 }21 return r.toString();22}23 24public static String generateValue(String param) {25 try {26 MessageDigest algorithm = MessageDigest.getInstance("MD5");27 algorithm.reset();28 algorithm.update(param.getBytes());29 byte[] messageDigest = algorithm.digest();30 return toHexString(messageDigest);31 } catch (Exception e) {32 throw new RuntimeException("生成Token失败", e);33 }34}35 }

View Code

Controller

@RestControllerpublic class WebController {private static final Logger LOGGER = LogManager.getLogger(WebController.class);@Autowiredprivate 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

1 @Service 2 public class ManagerService extends AbstractService { 3//12小时后过期 4private final static int EXPIRE = 3600 * 12 * 1000; 5 6public ManagerInfo getManagerInfo(String managerName) { 7 String sql = "select a.managerName, a.managerLevelId,a.managerId, a.password" 8 + " from manager a where a.managerName=?"; 9 ManagerInfo manager = jdbcDao.queryForObject(sql, new Object[] { managerName }, ManagerInfo.class);10 11 return manager;12}13 14public ManagerToken saveToken(Integer managerId) {15 ManagerToken managerToken = new ManagerToken();16 managerToken.managerId = managerId;17 18 //生成一个token19 managerToken.token = TokenGenerator.generateValue();20 //过期时间21 Date expireTime = new Date(System.currentTimeMillis() + EXPIRE);22 23 // 更新时间/过期时间24 SimpleDateFormat sm = new SimpleDateFormat("yyyyMMddHHmmss");25 Date systemDate = new Date();26 managerToken.updateTime = sm.format(systemDate);27 managerToken.expireTime = sm.format(expireTime);28 29 String sql = "insert into managertoken (managerId, token, updateTime, expireTime) values (?,?,?,?)"30 + " ON DUPLICATE KEY UPDATE token=?, updateTime=?, expireTime=?";31 jdbcDao.update(sql, new Object[]{managerToken.managerId, managerToken.token, managerToken.updateTime,32 managerToken.expireTime, managerToken.token, managerToken.updateTime, managerToken.expireTime});33 34 return managerToken;35}36 37@Transactional(propagation= Propagation.REQUIRED, isolation= Isolation.DEFAULT, readOnly = true, rollbackFor = Exception.class)38public ManagerInfo getManagerInfo(Integer managerId) {39 if (managerId == null) {40 return null;41 }42 43 String sql = "select a.managerId, a.managerName, a.managerLevelId from manager a " +44 "where a.managerId=?";45 ManagerInfo manager = jdbcDao.queryForObject(sql, new Object[]{managerId}, ManagerInfo.class);46 47 return manager;48}49 50public ManagerToken queryByToken(String token) {51 if (token == null || "".equals(token)) {52 return null;53 }54 55 String sql = "select managerid managerId, token, expireTime, updateTime from managertoken where token=?";56 ManagerToken managerToken = jdbcDao.queryForObject(sql, new Object[]{token}, ManagerToken.class);57 58 return managerToken;59}60 61 }

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。