赞
踩
之前用的是springsecurity+jwt作为安全验证,现在oauth2版本也可以实现,而且还有对外的token获取方式,所以替换成oauth2版本的springsecurity。
同样是使用jwt管理token,但是会加上redis,因为jwt生成的token是无状态的,所以生成新toekn后,旧token依旧能用,所以使用redis作为中间件来避免旧token还能使用的情况,实现单点登陆。
项目接口,红框里的是没用的,不用管
首先创建一个springboot项目,由于我用的是微服务,授权服务只是其中一个服务,所以可能会存在依赖缺少的情况,如果缺少依赖,评论留言。
然后是pom,pom中添加了项目中的其他模块,后面我会把源码上传至git
- <dependencies>
- <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>org.springframework.security.oauth.boot</groupId>
- <artifactId>spring-security-oauth2-autoconfigure</artifactId>
- <version>2.1.0.RELEASE</version>
- </dependency>
- <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-starter-thymeleaf</artifactId>
- </dependency>
- <!--自定义组件-->
- <dependency>
- <groupId>com.rmt</groupId>
- <artifactId>rmt-db-jpa</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.rmt</groupId>
- <artifactId>rmt-domain-authority</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.rmt</groupId>
- <artifactId>rmt-redis</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.rmt</groupId>
- <artifactId>rmt-common-region</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
- </dependencies>
创建数据库security_oauth2
- /*
- Navicat Premium Data Transfer
- Source Server : localhost
- Source Server Type : MySQL
- Source Server Version : 50717
- Source Host : localhost:3306
- Source Schema : security_oauth2
- Target Server Type : MySQL
- Target Server Version : 50717
- File Encoding : 65001
- Date: 05/03/2021 10:48:56
- */
-
- SET NAMES utf8mb4;
- SET FOREIGN_KEY_CHECKS = 0;
-
- -- ----------------------------
- -- Table structure for menu
- -- ----------------------------
- DROP TABLE IF EXISTS `menu`;
- CREATE TABLE `menu` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `menu_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单编号',
- `menu_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
- `router` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路由',
- `imgsrc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标地址',
- `index_num` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '序号',
- `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单类型(目录,菜单,按钮)',
- `perms` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识',
- `status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单状态(正常 ,停用)',
- PRIMARY KEY (`id`) USING BTREE,
- UNIQUE INDEX `MENU_UNIQUE`(`menu_code`) USING BTREE COMMENT 'menu唯一',
- UNIQUE INDEX `PERMS_UNIQUE`(`perms`) USING BTREE COMMENT 'perms唯一'
- ) ENGINE = InnoDB AUTO_INCREMENT = 171 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of menu
- -- ----------------------------
- INSERT INTO `menu` VALUES (1, '1000', '总菜单', NULL, NULL, '1', '菜单', NULL, '正常');
- INSERT INTO `menu` VALUES (9, '10000001', '系统管理', '', 'el-icon-user-solid', '0', '目录', 'system', '正常');
- INSERT INTO `menu` VALUES (11, '100000010002', '用户管理', '/UserIndex', '', '1', '菜单', 'system:user', '正常');
- INSERT INTO `menu` VALUES (12, '100000010003', '角色权限', '/RoleIndex', '', '2', '菜单', 'system:role', '正常');
- INSERT INTO `menu` VALUES (18, '100000010005', '菜单管理', '/MenuIndex', '', '3', '菜单', 'system:menu', '正常');
- INSERT INTO `menu` VALUES (40, '100000010006', '所属单位管理', '/OrganizationIndex', '', '4', '菜单', 'system:company', '正常');
- INSERT INTO `menu` VALUES (44, '100000010007', '日志管理', '/LogIndex', NULL, '5', '菜单', 'system:logs', '正常');
- INSERT INTO `menu` VALUES (75, '1000000100020001', '添加用户', '', '', '0', '按钮', 'system:user:add', '正常');
- INSERT INTO `menu` VALUES (76, '1000000100050001', '添加菜单', '', '', '0', '按钮', 'system:menu:add', '正常');
- INSERT INTO `menu` VALUES (77, '1000000100050002', '菜单列表', '', '', '0', '按钮', 'system:menu:list', '正常');
- INSERT INTO `menu` VALUES (78, '1000000100050003', '菜单修改', '', '', '0', '按钮', 'system:menu:update', '正常');
- INSERT INTO `menu` VALUES (79, '1000000100050004', '菜单删除', '', '', '0', '按钮', 'system:menu:delete', '正常');
- INSERT INTO `menu` VALUES (80, '1000000100020002', '用户修改', '', '', '0', '按钮', 'system:user:update', '正常');
- INSERT INTO `menu` VALUES (88, '1000000100020003', '用户删除', '', '', '0', '按钮', 'system:user:delete', '正常');
- INSERT INTO `menu` VALUES (89, '1000000100020004', '用户查询', '', '', '0', '按钮', 'system:user:select', '正常');
- INSERT INTO `menu` VALUES (90, '1000000100030001', '添加角色', '', '', '0', '按钮', 'system:role:add', '正常');
- INSERT INTO `menu` VALUES (91, '1000000100030002', '角色修改', '', '', '0', '按钮', 'system:role:update', '正常');
- INSERT INTO `menu` VALUES (92, '1000000100030003', '角色删除', '', '', '0', '按钮', 'system:role:delete', '正常');
- INSERT INTO `menu` VALUES (93, '1000000100030004', '菜单配置', '', '', '0', '按钮', 'system:role:config', '正常');
- INSERT INTO `menu` VALUES (94, '1000000100060001', '添加单位', '', '', '0', '按钮', 'system:company:add', '正常');
- INSERT INTO `menu` VALUES (95, '1000000100060002', '单位修改', '', '', '0', '按钮', 'system:company:update', '正常');
- INSERT INTO `menu` VALUES (96, '1000000100060003', '单位删除', '', '', '0', '按钮', 'system:company:delete', '正常');
- INSERT INTO `menu` VALUES (101, '1000000100070001', '日志查询', '', '', '0', '按钮', 'system:logs:select', '正常');
- INSERT INTO `menu` VALUES (112, '1000000100060004', '单位查询', '', '', '0', '按钮', 'system:company:select', '正常');
- INSERT INTO `menu` VALUES (135, '1000000100020005', '用户列表', '', '', '0', '按钮', 'system:user:list', '正常');
- INSERT INTO `menu` VALUES (137, '1000000100030005', '角色列表', '', '', '0', '按钮', 'system:role:list', '正常');
- INSERT INTO `menu` VALUES (143, '1000000100060005', '所属单位管理列表', '', '', '0', '按钮', 'system:company:list', '正常');
- INSERT INTO `menu` VALUES (146, '1000000100070002', '日志管理列表', '', '', '0', '按钮', 'system:logs:list', '正常');
- -- ----------------------------
- -- Table structure for oauth_client_details
- -- ----------------------------
- DROP TABLE IF EXISTS `oauth_client_details`;
- CREATE TABLE `oauth_client_details` (
- `client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '客户端的id(用于唯一标识每一个客户端(client);注册时必须填写(也可以服务端自动生成),这个字段是必须的,实际应用也有叫app_key)',
- `resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源服务器的id,多个用,(逗号)隔开{客户端能访问的资源id集合,注册客户端时,根据实际需要可选择资源id,也可以根据不同的额注册流程,赋予对应的额资源id}',
- `client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户端的秘钥{注册填写或者服务端自动生成,实际应用也有叫app_secret, 必须要有前缀代表加密方式}',
- `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指定客户端申请的权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔,如: \"read,write\".@EnableGlobalMethodSecurity(prePostEnabled = true)启用方法级权限控制然后在方法上注解标识@PreAuthorize(\"#oauth2.hasScope(\'read\')\")',
- `authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '认证的方式{可选值 授权码模式:authorization_code,密码模式:password,刷新token: refresh_token, 隐式模式: implicit: 客户端模式: client_credentials。支持多个用逗号分隔}',
- `web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '授权码模式认证成功跳转的地址{客户端重定向uri,authorization_code和implicit需要该值进行校验,注册时填写,}',
- `authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指定用户的权限范围,如果授权的过程需要用户登陆,该字段不生效,implicit和client_credentials需要',
- `access_token_validity` int(11) NULL DEFAULT NULL COMMENT 'token的过期时间{设置access_token的有效时间(秒),默认(60 * 60 * 12,12小时)}',
- `refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT '刷新token的过期时间{设置refresh_token有效期(秒),默认(60 *60 * 24 * 30, 30天)}',
- `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '值必须是json格式',
- `autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '默认false,适用于authorization_code模式,设置用户是否自动approval操作,设置true跳过用户确认授权操作页面,直接跳到redirect_uri',
- PRIMARY KEY (`client_id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of oauth_client_details
- -- ----------------------------
- INSERT INTO `oauth_client_details` VALUES ('demo-client', NULL, '$2a$10$tj/PXVj9MBRdyuBKq99zeOw6oGkPVe7HNOxjmBWh.hsRmaU4IT2Ba', 'all', 'authorization_code,refresh_token,password', 'http://localhost:17772/home', NULL, 3600, 36000, NULL, '1');
-
- -- ----------------------------
- -- Table structure for organization
- -- ----------------------------
- DROP TABLE IF EXISTS `organization`;
- CREATE TABLE `organization` (
- `id` bigint(11) NOT NULL AUTO_INCREMENT,
- `organization_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '组织机构编号',
- `organization_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组织机构名称',
- `savetime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '时间',
- `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
- PRIMARY KEY (`id`) USING BTREE,
- UNIQUE INDEX `uk_ code`(`organization_code`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 65 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of organization
- -- ----------------------------
- INSERT INTO `organization` VALUES (1, '1000', '总组织', '1607496672156', '父集团');
-
- -- ----------------------------
- -- Table structure for role
- -- ----------------------------
- DROP TABLE IF EXISTS `role`;
- CREATE TABLE `role` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '描述',
- PRIMARY KEY (`id`) USING BTREE,
- UNIQUE INDEX `nameunique`(`name`) USING BTREE COMMENT 'role name唯一'
- ) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of role
- -- ----------------------------
- INSERT INTO `role` VALUES (1, 'ROLE_ADMIN', '超级管理员');
- INSERT INTO `role` VALUES (2, 'ROLE_TEST', '测试用户');
- INSERT INTO `role` VALUES (6, 'ROLE_OR', '普通用户');
-
- -- ----------------------------
- -- Table structure for role_menu
- -- ----------------------------
- DROP TABLE IF EXISTS `role_menu`;
- CREATE TABLE `role_menu` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `r_id` int(11) NULL DEFAULT NULL,
- `m_id` int(11) NULL DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 6857 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of role_menu
- -- ----------------------------
- INSERT INTO `role_menu` VALUES (6709, 1, 11);
- INSERT INTO `role_menu` VALUES (6710, 1, 75);
- INSERT INTO `role_menu` VALUES (6711, 1, 80);
- INSERT INTO `role_menu` VALUES (6712, 1, 88);
- INSERT INTO `role_menu` VALUES (6713, 1, 89);
- INSERT INTO `role_menu` VALUES (6714, 1, 135);
-
- -- ----------------------------
- -- Table structure for user
- -- ----------------------------
- DROP TABLE IF EXISTS `user`;
- CREATE TABLE `user` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT,
- `iphone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `organization_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `organization_num` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `savetime` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
- `real_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实姓名',
- `user_type` enum('巡查员','管理员') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '巡查员' COMMENT '用户类型',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 72 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of user
- -- ----------------------------
- INSERT INTO `user` VALUES (1, '15227686967', '总组织', '1000', '$2a$10$7oxG1a5hefbal86RlTJnVOc5TXR6gcDCxWSP69H/mgAVL63wJ3F6S', '2020-05-28 13:42:15.000000', '2', 'admin', 'xiayanhui', '巡查员');
-
- -- ----------------------------
- -- Table structure for user_role
- -- ----------------------------
- DROP TABLE IF EXISTS `user_role`;
- CREATE TABLE `user_role` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `u_id` int(11) NULL DEFAULT NULL,
- `r_id` int(11) NULL DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-
- -- ----------------------------
- -- Records of user_role
- -- ----------------------------
- INSERT INTO `user_role` VALUES (1, 1, 1);
- INSERT INTO `user_role` VALUES (6, 13, 1);
- INSERT INTO `user_role` VALUES (19, 49, 8);
- INSERT INTO `user_role` VALUES (32, 62, 1);
- INSERT INTO `user_role` VALUES (33, 63, 6);
- INSERT INTO `user_role` VALUES (34, 64, 6);
- INSERT INTO `user_role` VALUES (35, 65, 1);
- INSERT INTO `user_role` VALUES (36, 66, 1);
- INSERT INTO `user_role` VALUES (37, 67, 1);
- INSERT INTO `user_role` VALUES (38, 68, 1);
- INSERT INTO `user_role` VALUES (39, 69, 1);
- INSERT INTO `user_role` VALUES (40, 70, 1);
- INSERT INTO `user_role` VALUES (41, 71, 1);
-
- SET FOREIGN_KEY_CHECKS = 1;
然后修改配置文件,bootstrap.yml
从配置文件应该可以看出,我使用的是nacos,所以nacos上也会存在一些配置文件。
- server:
- port: 17772
- ip-address: localhost
- namespace-id: 2120ba90-bbee-464c-bcff-62b039803d97
- spring:
- aop:
- auto: true
- application:
- name: oauth2-server
- cloud:
- nacos:
- discovery:
- #server-addr: 192.168.0.10:8848
- server-addr: ${ip-address}:8848
- #此处的namespace是discovery服务对应的命名空间,与config不同
- namespace: ${namespace-id}
- config:
- server-addr: ${ip-address}:8848
- file-extension: yaml
- #此处只是对应config的命名空间
- namespace: ${namespace-id}
- #共享配置文件
- shared-configs:
- - data-id: shared.yaml
- group: dev
- refresh: true
- - data-id: kafka-producer.yaml
- group: dev
- refresh: true
- - data-id: redis-config.yaml
- group: dev
- refresh: true
- #数据源
- datasource:
- name: db-base
- url: jdbc:mysql://${ip-address}:3306/security_oauth2?serverTimezone=GMT%2B8
- username: root
- password: root
- driver-class-name: com.mysql.cj.jdbc.Driver
- thymeleaf:
- prefix: classpath:/views/
- suffix: .html
- cache: false
- mvc:
- throw-exception-if-no-handler-found: true
然后开始配置授权服务
创建AuthServerConfig 里面注解很全
- package com.zz.zzoauth2.config;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
- import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
- import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
- import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
- import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
- import org.springframework.security.oauth2.provider.token.TokenEnhancer;
- import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
-
- import javax.sql.DataSource;
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * OAuth2的授权服务:主要作用是OAuth2的客户端进行认证与授权
- *
- * @author wqy
- * @date 2020-09-04
- */
- @Configuration
- @EnableAuthorizationServer
- public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
-
- /**
- * 从数据库中查询账号密码
- */
- @Autowired
- @Qualifier("cusUserDetailsService")
- public UserDetailsService userDetailsService;
-
- /**
- * 从spring中获取数据源
- */
- @Autowired
- private DataSource dataSource;
-
- /**
- * 认证管理器
- */
- @Autowired
- private AuthenticationManager authenticationManager;
-
- /**
- * 它就是用来保存token
- */
- @Autowired
- private TokenStore jwtTokenStore;
-
- /**
- * TokenEnhancer的子类,帮助程序在JWT编码的令牌值和OAuth身份验证信息之间进行转换(在两个方向上),
- * 同时充当TokenEnhancer授予令牌的时间。
- * 自定义的JwtAccessTokenConverter(把自己设置的jwt签名加入accessTokenConverter中)
- */
- @Autowired
- private JwtAccessTokenConverter jwtAccessTokenConverter;
-
- /**
- * 在AuthorizationServerTokenServices 实现存储访问令牌之前增强访问令牌的策略。
- */
- @Autowired
- private TokenEnhancer jwtTokenEnhancer;
-
- /**
- * 配置OAuth2的客户端信息:clientId、client_secret、authorization_type、redirect_url等。
- * 实际保存在数据库中,建表语句在resource下data中
- *
- * 注:主要是与数据库互相同步
- * @param clients
- * @throws Exception
- */
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- clients.jdbc(dataSource);
- }
-
- /**
- * 1.增加jwt 增强模式
- * 2.调用userDetailsService实现UserDetailsService接口,对客户端信息进行认证与授权
- * @param endpoints
- * @throws Exception
- */
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- /**
- * jwt 增强模式
- * 对令牌的增强操作就在enhance方法中
- * 下面在配置类中,将TokenEnhancer和JwtAccessConverter加到一个enhancerChain中
- *
- * 通俗点讲它做了两件事:
- * 给JWT令牌中设置附加信息和jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
- * 判断请求中是否有refreshToken,如果有,就重新设置refreshToken并加入附加信息
- */
- TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
- List<TokenEnhancer> enhancerList = new ArrayList<TokenEnhancer>();
- enhancerList.add(jwtTokenEnhancer);
- enhancerList.add(jwtAccessTokenConverter);
- //将自定义Enhancer加入EnhancerChain的delegates数组中
- enhancerChain.setTokenEnhancers(enhancerList);
- endpoints.tokenStore(jwtTokenStore)
- .userDetailsService(userDetailsService)
- /**
- * 支持 password 模式
- */
- .authenticationManager(authenticationManager)
- .tokenEnhancer(enhancerChain)
- .accessTokenConverter(jwtAccessTokenConverter);
- // 最后一个参数为替换之后授权页面的url
- endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");
- }
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security
- .tokenKeyAccess("permitAll()")
- .checkTokenAccess("isAuthenticated()")
- .allowFormAuthenticationForClients();
- }
-
- }
-
然后创建JwtTokenConfig,此处主要是对JWT进行的配置,设置密钥,在资源服务器要使用相同的密钥
- package com.zz.zzoauth2.config;
-
- import com.zz.constant.Oauth2Constant;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.oauth2.provider.token.TokenEnhancer;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
- import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
-
- import java.util.HashMap;
-
- /**
- * JwtTokenConfig配置类
- * 使用TokenStroe将进入JwtTokenStore
- * 注:Spring-Sceurity使用TokenEnhancer和JwtAccessConverter增强jwt令牌
- * @author wqy
- * @version 1.0
- * @date 2021/3/3 14:15
- */
- @Configuration
- public class JwtTokenConfig {
-
- @Bean
- public TokenStore jwtTokenStore(){
- return new JwtTokenStore(jwtAccessTokenConverter());
- }
-
- /**
- * JwtAccessTokenConverter:TokenEnhancer的子类,帮助程序在JWT编码的令牌值和OAuth身份验证信息之间进行转换(在两个方向上),同时充当TokenEnhancer授予令牌的时间。
- * 自定义的JwtAccessTokenConverter:把自己设置的jwt签名加入accessTokenConverter中(这里设置'demo',项目可将此在配置文件设置)
- * @return
- */
- @Bean
- public JwtAccessTokenConverter jwtAccessTokenConverter() {
- JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
- accessTokenConverter.setSigningKey(Oauth2Constant.JWT_SIGNING_KEY);
- return accessTokenConverter;
- }
-
- /**
- * 引入自定义JWTokenEnhancer:
- * 自定义JWTokenEnhancer实现TokenEnhancer并重写enhance方法,将附加信息加入oAuth2AccessToken中
- * @return
- */
- @Bean
- public TokenEnhancer jwtTokenEnhancer(){
- return new JwtTokenEnhancer();
- }
-
- }
然后创建JwtTokenEnhancer作为token增强,因为oauth2生成的token非常的短,而且不能存储信息,所以旧使用增强器,来存储更多的信息,同样生成的token也会更长。
TODO可以看一下,使用redis作为中间件,来实现旧token的伪失效
- package com.zz.zzoauth2.config;
-
- import com.zz.zzoauth2.bean.ContextBeans;
- import com.zz.zzoauth2.domain.AuthUser;
- import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
- import org.springframework.security.oauth2.common.OAuth2AccessToken;
- import org.springframework.security.oauth2.provider.OAuth2Authentication;
- import org.springframework.security.oauth2.provider.token.TokenEnhancer;
-
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
-
- /**
- * TokenEnhancer:在AuthorizationServerTokenServices 实现存储访问令牌之前增强访问令牌的策略。
- * 自定义TokenEnhancer的代码:把附加信息加入oAuth2AccessToken中
- *
- * @author Tom
- * @date 2020-09-04
- */
- public class JwtTokenEnhancer implements TokenEnhancer {
-
-
- /**
- * 重写enhance方法,将附加信息加入oAuth2AccessToken中
- * @param oAuth2AccessToken
- * @param oAuth2Authentication
- * @return
- */
- @Override
- public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
- //TODO:token过期可以在此处想办法,结合redis,username可以做redis的key,value为jtl,
- // 在这生成一条以username为key的redis记录,
- // 资源服务根据解析出来的token,在redis中查找是否有相同的数据,有的话验证token中的jtl是否一致,
- // 不一致则提示token异常并删除redis中的该username,此处一定要保证redis中用户对应username的唯一性,
- // 避免大量并发击穿redis。
- // 单点登陆核心就是保证token的唯一性,现在保证jtl的唯一性就是保证token的唯一性
- // 步骤:
- // 1.获取jtl和到期时间
- // 2.以username为key并设置ttl(过期时间)
- // 3.由于系统里用户名是唯一的,所以存redis前要判断是否有重复的,有重复则删掉
- // 4.存入reids并设置ttl
- try {
- Map<String, Object> map = new HashMap<String, Object>();
- AuthUser authUser = (AuthUser) oAuth2Authentication.getPrincipal();
- authUser.getUser().setPassword(null);
- authUser.setPassword(null);
- map.put("authUser",authUser);
- ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);
- //是否启用redis
- if(ContextBeans.systemParam.getIsOpenOauth2Redis()){
- ContextBeans.redisTemplateUtils.set(authUser.getUsername(),oAuth2AccessToken.getValue(),oAuth2AccessToken.getExpiresIn(),TimeUnit.SECONDS);
- }
- }catch (Exception e){
- e.printStackTrace();
- }
- return oAuth2AccessToken;
-
- }
- }
-
创建SecurityConfig,此配置是关于security的配置,security的过滤器级别高于oauth2,所以要注意
- package com.zz.zzoauth2.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
- import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
-
- /**
- * @author wqy
- * @version 1.0
- * @date 2021/3/3 14:08
- */
- @Configuration
- @EnableAuthorizationServer
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- /**
- * 引入密码加密类
- * @return
- */
- @Bean
- public PasswordEncoder passwordEncoder(){
- return new BCryptPasswordEncoder();
- }
-
- /**
- * 支持密码模式配置
- * @return
- * @throws Exception
- */
- @Override
- @Bean
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- /**
- * 配置URL访问授权,必须配置authorizeRequests(),否则启动报错,说是没有启用security技术。
- * 注意:在这里的身份进行认证与授权没有涉及到OAuth的技术:当访问要授权的URL时,请求会被DelegatingFilterProxy拦截,
- * 如果还没有授权,请求就会被重定向到登录界面。在登录成功(身份认证并授权)后,请求被重定向至之前访问的URL。
- * @param http
- * @throws Exception
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 必须配置,不然OAuth2的http配置不生效----不明觉厉
- .requestMatchers()
- .antMatchers("/auth/login", "/auth/authorize","/oauth/authorize")
- .and()
- .authorizeRequests()
- // 自定义页面或处理url是,如果不配置全局允许,浏览器会提示服务器将页面转发多次
- .antMatchers("/auth/login", "/auth/authorize")
- .permitAll()
- .anyRequest()
- .authenticated();
-
- // 表单登录
- http.formLogin()
- // 登录页面
- .loginPage("/auth/login")
- // 登录处理url
- .loginProcessingUrl("/auth/authorize");
- http.httpBasic().disable();
-
- }
- }
然后CusUserDetailsService,连接数据库,登陆等等都是从此获取的数据
- package com.zz.zzoauth2.service;
-
- import com.zz.domain.authority.Organization;
- import com.zz.domain.authority.User;
- import com.zz.zzoauth2.domain.AuthUser;
- import com.zz.zzoauth2.resp.OrganizationJpa;
- import com.zz.zzoauth2.resp.RoleJpa;
- import com.zz.zzoauth2.resp.UserJpa;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Component;
-
- /**
- * Spring-Security自定义身份认证类(实现UserDetailsService并重写loadUserByUsername方法)
- * 在loadUserByUsername方法内校验用户名密码是否正确并返回一个UserDetails对象
- *
- * @author Tom
- * @date 2020-09-04
- */
- @Component(value = "cusUserDetailsService")
- public class CusUserDetailsService implements UserDetailsService {
-
- private final UserJpa userJpa;
- private final RoleJpa roleJpa;
- private final OrganizationJpa organizationJpa;
-
- public CusUserDetailsService(UserJpa userJpa, RoleJpa roleJpa, OrganizationJpa organizationJpa) {
- this.userJpa = userJpa;
- this.roleJpa = roleJpa;
- this.organizationJpa = organizationJpa;
- }
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-
- User user = userJpa.findByUsername(username);
-
- Organization organization = organizationJpa.findNameByCode(user.getOrganizationNum());
-
- if (user == null) {
- throw new UsernameNotFoundException("user: " + username + " is not found.");
- }
-
- return new AuthUser(user.getUsername()
- , user.getPassword()
- , roleJpa.findByUserRole(user.getId())
- ,organization
- ,user);
- }
-
- }
上面就是一些配置代码和流程
配置实在是太多了,我就不一一配置, 看源码比较简单。
自定义授权和登陆页面我简单介绍一下,其实就是在配置AuthServerConfig 中配置授权页,因为授权是属于oauth2的,然后在SecurityConfig中配置登陆页,这两个分别对应的是Controller中的请求路由,
然后就可以跳转到对应的页面,源码中使用的是thymeleaf,页面在Resuorce下的views中。
配置security-oauth2主要还是要理解思路和过程,理解什么是授权,由于oauth2中有四种模式,源码可以使用鉴权码模式和密码模式,这两种也是最常用的。
源码中zz-security-oauth2-server下product.md中有使用介绍和简单介绍。
源码正在上传,下一章奉上
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。