之前用的是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中有使用介绍和简单介绍。
源码正在上传,下一章奉上