说明
基于mysql数据库存储client和用户数据,实现认证功能
基于redis实现springboot+security+oauth+mysql用户认证功能
基于jwt实现springboot+oauth2用户认证功能
框架
springboot 2.x + oauth2 + mysql
搭建过程如下:
1、建表语句
DROP TABLE IF EXISTS `oauth_access_token`;CREATE TABLE `oauth_access_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token的值', `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据', `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope', `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID', `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据', `refresh_token` varchar(255) DEFAULT NULL COMMENT '加密的refresh_token的值') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_approvals`;CREATE TABLE `oauth_approvals` ( `userId` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `clientId` varchar(255) DEFAULT NULL COMMENT '客户端ID', `scope` varchar(255) DEFAULT NULL COMMENT '申请的权限范围', `status` varchar(10) DEFAULT NULL COMMENT '状态(Approve或Deny)', `expiresAt` datetime DEFAULT NULL COMMENT '过期时间', `lastModifiedAt` datetime DEFAULT NULL COMMENT '最终修改时间') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details` ( `client_id` varchar(255) NOT NULL COMMENT '客户端ID', `resource_ids` varchar(255) DEFAULT NULL COMMENT '资源ID集合,多个资源时用逗号(,)分隔', `client_secret` varchar(255) DEFAULT NULL COMMENT '客户端密匙', `scope` varchar(255) DEFAULT NULL COMMENT '客户端申请的权限范围', `authorized_grant_types` varchar(255) DEFAULT NULL COMMENT '客户端支持的grant_type', `web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT '重定向URI', `authorities` varchar(255) DEFAULT NULL COMMENT '客户端所拥有的Spring Security的权限值,多个用逗号(,)分隔', `access_token_validity` int(11) DEFAULT NULL COMMENT '访问令牌有效时间值(单位:秒)', `refresh_token_validity` int(11) DEFAULT NULL COMMENT '更新令牌有效时间值(单位:秒)', `additional_information` varchar(255) DEFAULT NULL COMMENT '预留字段', `autoapprove` varchar(255) DEFAULT NULL COMMENT '用户是否自动Approval操作') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_client_token`;CREATE TABLE `oauth_client_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token值', `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据', `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope', `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_code`;CREATE TABLE `oauth_code` ( `code` varchar(255) DEFAULT NULL COMMENT '授权码(未加密)', `authentication` varbinary(255) DEFAULT NULL COMMENT 'AuthorizationRequestHolder.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_refresh_token`;CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密过的refresh_token的值', `token` longblob COMMENT 'OAuth2RefreshToken.java对象序列化后的二进制数据 ', `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `tb_permission`;
CREATE TABLE `tb_permission`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父权限',
`name` varchar(64) NOT NULL COMMENT '权限名称',
`enname` varchar(64) NOT NULL COMMENT '权限英文名称',
`url` varchar(255) NOT NULL COMMENT '授权路径',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 44
DEFAULT CHARSET = utf8 COMMENT ='权限表';
insert into `tb_permission`(`id`, `parent_id`, `name`, `enname`, `url`, `description`, `created`, `updated`)
values (37, 0, '系统管理', 'System', '/', NULL, '2019-04-04 23:22:54', '2019-04-04 23:22:56'),
(38, 37, '用户管理', 'SystemUser', '/users/', NULL, '2019-04-04 23:25:31', '2019-04-04 23:25:33'),
(39, 38, '查看用户', 'SystemUserView', '', NULL, '2019-04-04 15:30:30', '2019-04-04 15:30:43'),
(40, 38, '新增用户', 'SystemUserInsert', '', NULL, '2019-04-04 15:30:31', '2019-04-04 15:30:44'),
(41, 38, '编辑用户', 'SystemUserUpdate', '', NULL, '2019-04-04 15:30:32', '2019-04-04 15:30:45'),
(42, 38, '删除用户', 'SystemUserDelete', '', NULL, '2019-04-04 15:30:48', '2019-04-04 15:30:45');
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父角色',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`enname` varchar(64) NOT NULL COMMENT '角色英文名称',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 38
DEFAULT CHARSET = utf8 COMMENT ='角色表';
insert into `tb_role`(`id`, `parent_id`, `name`, `enname`, `description`, `created`, `updated`)
values (37, 0, '超级管理员', 'admin', NULL, '2019-04-04 23:22:03', '2019-04-04 23:22:05');
DROP TABLE IF EXISTS `tb_role_permission`;
CREATE TABLE `tb_role_permission`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
`permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 43
DEFAULT CHARSET = utf8 COMMENT ='角色权限表';
insert into `tb_role_permission`(`id`, `role_id`, `permission_id`)
values (37, 37, 37),
(38, 37, 38),
(39, 37, 39),
(40, 37, 40),
(41, 37, 41),
(42, 37, 42);
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码,加密存储',
`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
`email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `phone` (`phone`) USING BTREE,
UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 38
DEFAULT CHARSET = utf8 COMMENT ='用户表';
insert into `tb_user`(`id`, `username`, `password`, `phone`, `email`, `created`, `updated`)
values (37, 'admin', '$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi', '15888888888',
'lee.lusifer@gmail.com', '2019-04-04 23:21:27', '2019-04-04 23:21:29');
DROP TABLE IF EXISTS `tb_user_role`;
CREATE TABLE `tb_user_role`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户 ID',
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 38
DEFAULT CHARSET = utf8 COMMENT ='用户角色表';
insert into `tb_user_role`(`id`, `user_id`, `role_id`)
values (37, 37, 37);
表说明如下:
真正用到了红线内的表,后续用户数据也只是在代码里模拟
加入client数据
INSERT INTO `oauth`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('user_client', '', '123456', 'read,write', 'password,refresh_token', '', '', 7200, 1800, NULL, 'true');
2、pom依赖
<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</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
3、重写UserDetailsService接口实现
package com.yy.oauthmysql.service.impl;
import com.yy.oauthmysql.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
/*模拟数据库操作*/
User user = new User();
user.setUsername("10086");
user.setPassword("123456");
return new org.springframework.security.core.userdetails
.User(user.getUsername(), user.getPassword(), true, true, true, true, Collections.EMPTY_SET);
}
}
4、websecurityconfig配置
package com.yy.oauthmysql.config;
import com.yy.oauthmysql.service.impl.UserDetailServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.swing.*;
/**
* @author code
* @Date 2022/7/6 10:50
* Description security config
* Version 1.0
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new UserDetailServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
return new CustomPasswordEncoder();
}
/**
* 用于支持 password 模式 密码模式需要 AuthenticationManager 支持
* password 模式一点要加上这个
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 允许匿名访问所有接口 主要是 oauth 接口
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").permitAll();
// 将登录框关闭
http.formLogin().disable();
}
}
5、加密说明
报错: Encoded password does not look like BCrypt
原因:SpringBoot版本的问题,1.x是明文,2.x是有加密
解决:
方式一:
使用 NoOpPasswordEncoder.getInstance();
方式二:
重写加密方法
package com.yy.oauthmysql.config;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author code
* @Date 2022/7/7 14:11
* Description 明文加密
* Version 1.0
*/
public class CustomPasswordEncoder implements PasswordEncoder {
@Override
public boolean upgradeEncoding(String encodedPassword) {
return false;
}
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}
6、认证服务器AuthorizationServerConfig
package com.yy.oauthmysql.config;
import com.yy.oauthmysql.service.impl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;
/**
* @author code
* @Date 2022/7/6 10:54
* Description 认证服务器配置
* Version 1.0
*/
@Configuration
@EnableAuthorizationServer //注解开启了验证服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
// 声明TokenStore实现
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
// 声明 ClientDetails实现
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsService clientDetails;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Bean
public UserDetailsService userDetailsService(){
return new UserDetailServiceImpl();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
endpoints.userDetailsService(userDetailsService());
endpoints.setClientDetailsService(clientDetails);
//配置TokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
endpoints.tokenServices(tokenServices);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore);
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
7、资源服务器ResourceServerConfiguration
package com.yy.oauthmysql.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* @author code
* @Date 2022/7/6 15:46
* Description 资源服务器
* Version 1.0
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
}
}
8、配置文件
server:
port: 9010
spring:
application:
name: oauth2-as-jdbc
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
main:
allow-bean-definition-overriding: true
security:
user:
name: admin
password: admin
9、获取token
数据库表oauth_access_token
10、验证token