在明白了Oauth2.0到底是个什么玩意儿,以及它的四种模式之后,接下来我们通过SpringSecurity来实现一下授权码模式,这种模式是市面上用的最广的。项目框架依然是以mall这个项目为主。但是会创建一个单独的认证服务器模块(mall-oauth2)和资源服务器模块(mall-resource)。
2.1 认证服务器认证服务器是服务提供者专门用来处理认证授权的服务器,主要负责获取用户授权并颁发token,以及完成后续的token认证工作。认证部分功能主要由spring security 负责,授权则由oauth2负责。
2.1.1 项目搭建创建一个springboot项目 mall-oauth2项目,配置pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>mall</artifactId>
<groupId>com.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>mall-oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mall-oauth2</name>
<description>认证服务器</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
</project>
2.1.2 表结构设计
1.在oauth2库中增加表结构
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL,
`refresh_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'false',
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` blob NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO oauth_client_details(client_id,resource_ids,client_secret,scope,authorized_grant_types,web_server_redirect_uri,autoapprove) VALUES('iqiyi','resource1','$2a$10$m7rm4duMVCXbRAC0YChzsu8irZmAngLRHwJpBzPZjtT72yDxRYHVC','all','authorization_code','https://www.iqiyi.com','true');
- oauth_access_token:存储生成的access_token,由类JdbcTokenStore操作
- oauth_client_details:存储客户端的配置信息,由类JdbcClientDetailsService操作
- oauth_code:存储服务端系统生成的code的值,由类JdbcAuthorizationCodeServices操作
- oauth_refresh_token:存储刷新令牌的refresh_token,如果客户端的grant_type不支持refresh_token,那么不会用到这张表,同样由类JdbcTokenStore操作
2.然后创建用户和角色表
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`parent_id` bigint(11) NULL DEFAULT NULL COMMENT '用户ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '权限名字',
`ename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '权限名字',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '请求路径',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '描述',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 0, '系统管理', 'System', '/', NULL, '2019-05-30 16:22:20', '2019-05-30 16:22:24');
INSERT INTO `permission` VALUES (2, 0, '用户管理', 'SystemUser', '/users', NULL, '2019-05-30 16:23:28', '2019-05-30 16:23:32');
INSERT INTO `permission` VALUES (3, 0, '查看用户', 'SystemUserView', NULL, NULL, '2019-05-30 16:24:29', '2019-05-30 16:24:33');
INSERT INTO `permission` VALUES (4, 0, '新增用户', 'SystemUserInsert', NULL, NULL, '2019-05-30 16:25:09', '2019-05-30 16:25:13');
INSERT INTO `permission` VALUES (5, 0, '编辑用户', 'SystemUserUpdate', NULL, NULL, '2019-05-30 16:25:53', '2019-05-30 16:25:57');
INSERT INTO `permission` VALUES (6, 0, '删除用户', 'SystemUserDelete', NULL, NULL, '2019-05-30 16:26:49', '2019-05-30 16:26:54');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父类ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色名字',
`ename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色名字',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '描述',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 0, '超级管理员', 'ADMIN', NULL, '2019-05-30 16:09:53', '2019-05-30 16:09:57');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` bigint(11) NOT NULL COMMENT '主键ID',
`role_id` bigint(11) NULL DEFAULT NULL COMMENT '角色ID',
`permission_id` bigint(11) NULL DEFAULT NULL COMMENT '权限ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
INSERT INTO `role_permission` VALUES (3, 1, 3);
INSERT INTO `role_permission` VALUES (4, 1, 4);
INSERT INTO `role_permission` VALUES (5, 1, 5);
INSERT INTO `role_permission` VALUES (6, 1, 6);
-- ----------------------------
-- Table structure for my_user
-- ----------------------------
DROP TABLE IF EXISTS `my_user`;
CREATE TABLE `my_user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密码',
`gender` int(2) NULL DEFAULT NULL COMMENT '性别(1男 2女)',
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮箱',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '用户创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`removed` int(2) NULL DEFAULT NULL COMMENT '是否删除(1删除0未删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of my_user
-- ----------------------------
INSERT INTO `my_user` VALUES (1, 'admin', '$2a$10$BDRAkgioU9jqN8eNVrRcluQd9GcocNv3EB8VfdKYfW8iR3vSbY3pW', 1, NULL, '2019-05-30 15:53:45', '2019-05-30 15:53:51', 0);
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` bigint(11) NOT NULL COMMENT '主键ID',
`user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户主键',
`role_id` bigint(11) NULL DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
SET FOREIGN_KEY_CHECKS = 1;
2.1.3 配置
1.配置文件
# 应用名称
server.port=7070
spring.application.name=mall-oauth2
spring.thymeleaf.cache=false
spring.datasource.druid.url=jdbc:mysql://192.168.8.74:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=30
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.filters=stat,wall
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.auto-mapping-behavior=full
mybatis-plus.mapper-locations=classpath*:mapper/*Mapper.xml
2.开启Spring Security 安全配置
创建WebSecurityConfig 继承WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
通过@EnableWebSecurity 开启Spring Security配置,继承WebSecurityConfigurerAdapter的方法,实现个性化配置。如果使用内存配置用户,可以重写其中的configure方法进行配置,由于我们使用数据库中的用户信息,所以不需要在这里进行配置。并且采用认证服务器和资源服务器分离,也不需要在这里对服务资源进行权限的配置。
在类中创建了两个Bean,分别是用于处理认证请求的认证管理器AuthenticationManager,以及配置全局统一使用的密码加密方式BCryptPasswordEncoder,它们会在认证服务中被使用。
3.创建认证服务配置类
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager; //认证管理器
@Autowired
private BCryptPasswordEncoder passwordEncoder;//密码加密方式
@Autowired
private DataSource dataSource; // 注入数据源
@Autowired
private UserDetailsService userDetailsService; //自定义用户身份认证
@Bean
public ClientDetailsService jdbcClientDetailsService(){
//将client信息存储在数据库中
return new JdbcClientDetailsService(dataSource);
}
@Bean
public TokenStore tokenStore(){
//对token进行持久化存储在数据库中,数据存储在oauth_access_token和oauth_refresh_token
return new JdbcTokenStore(dataSource);
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
//加入对授权码模式的支持
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//设置客户端的配置从数据库中读取,存储在oauth_client_details表
clients.withClientDetails(jdbcClientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())//token存储方式
.authenticationManager(authenticationManager)// 开启密码验证,来源于 WebSecurityConfigurerAdapter
.userDetailsService(userDetailsService)// 读取验证用户的信息
.authorizationCodeServices(authorizationCodeServices())
.setClientDetailsService(jdbcClientDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 配置Endpoint,允许请求,不被Spring-security拦截
security.tokenKeyAccess("permitAll()") // 开启/oauth/token_key 验证端口无权限访问
.checkTokenAccess("isAuthenticated()") // 开启/oauth/check_token 验证端口认证权限访问
.allowFormAuthenticationForClients()// 允许表单认证
.passwordEncoder(passwordEncoder); // 配置BCrypt加密
}
}
在类中,通过@EnableAuthorizationServer 注解开启认证服务,通过继承父类AuthorizationServerConfigurerAdapter,对以下信息进行了配置:
ClientDetailsServiceConfigurer:配置客户端服务,这里我们通过JdbcClientDetailsService从数据库读取相应的客户端配置信息,进入源码可以看到客户端信息是从表oauth_client_details中拉取。
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点,以及令牌服务的配置信息。该类作为一个装载类,装载了Endpoints所有的相关配置。
AuthorizationServerSecurityConfigurer:配置令牌端点(endpoint)的安全约束,OAuth2开放了端点用于检查令牌,/oauth/check_token和/oauth/token_key这些端点默认受到保护,在这里配置可被外部调用。
4.采用从数据库中获取用户信息的方式进行身份验证
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
MyUser myUser = userService.getUserByUserName(userName);
if (myUser==null){
throw new UsernameNotFoundException("username : " userName " is not exist");
}
List<GrantedAuthority> authorities=new ArrayList<>();
//获取用户权限
List<Permission> permissions = permissionService.getByUserId(myUser.getId());
permissions.forEach(permission->{
authorities.add(new SimpleGrantedAuthority(permission.getEname()));
});
return new User(myUser.getUsername(),myUser.getPassword(),authorities);
}
}
创建UserDetailServiceImpl 实现UserDetailsService接口,并实现loadUserByUsername方法,根据用户名从数据库查询用户信息及权限。
5.创建MyUser和Permission等实体类,具体参考代码
2.1.4 启动服务首先发起请求获取授权码(code),直接访问下面的url:
http://localhost:7070/oauth/authorize?client_id=iqiyi&response_type=code
- client_id:因为认证服务器要知道是哪一个应用在请求授权,所以client_id就是认证服务器给每个应用分配的id
- redirect_uri:重定向地址,会在这个重定向地址后面附加授权码,让第三方应用获取code
- response_type:code表明采用授权码认证模式
- scope:需要获得哪些授权,这个参数的值是由服务提供商定义的,不能随意填写
首先会重定向到登录验证页面,因为之前的url中只明确了第三方应用的身份,这里要确定第三方应用要请求哪一个用户的授权。输入数据库表user中配置的用户信息 admin/123456:
注意url中请求的参数必须和在数据库中的表oauth_client中配置的相同,如果不存在或信息不一致都会报错,在参数填写错误时会产生如下报错信息:
如果参数完全匹配,会请求用户向请求资源的客户端client授权,因为我数据库的autoapprove字段设置为true了,所以直接会进行授权。并且页面会跳转到redirect_uri定义的地址,并在uri后面带上code授权码。
这样,用户的登录和授权的操作都在浏览器中完成了,接下来我们需要获取令牌,发送post请求到/oauth/token接口,使用授权码获取access_token。在发送请求时,需要在请求头中包含clientId和clientSecret,并且携带参数 grant_type、code、redirect_uri,这里会对redirect_uri做二次验证: