搭建spring-security-oauth2授权服务(服务)和资源服务(模块)(一)—— 搭建授权服务

之前用的是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中有使用介绍和简单介绍。

源码正在上传,下一章奉上

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值