导入所有公共依赖:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.4.RELEASEversion>
<relativePath/>
parent>
<packaging>pompackaging>
<modules>
<module>server-authoritymodule>
<module>server-resourcemodule>
<module>knowledgemodule>
modules>
<groupId>com.hhgroupId>
<artifactId>authorityartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.12version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.7.20version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.10.5version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.10.5version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.10.5version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
因为资源服务器服务 server-authority 和授权服务器服务 server-resource 会依赖一些公共的配置类,为了防止搭建资源服务器和授权服务器的时候都需要定义这些配置类,且其他业务服务也需要依赖资源服务器 server-authority 服务,因此我这里将资源服务器定义为了一个 starter,这样就可以在授权服务器服务 server-authority 和其他业务服务中引入该 starter。
因为 server-resource 项目是 springsecurity-oauth2 项目的子项目,根据maven依赖的传递性,父级项目springsecurity-oauth2 导入的依赖在子项目中都可以使用,子项目只需要新增父项目中没有的依赖即可:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>authorityartifactId>
<groupId>com.hhgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>server-resourceartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<version>2.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-oauth2-resource-serverartifactId>
dependency>
dependencies>
project>
token存储方式配置类TokenStoreAutoConfiguration在server-resource和 server-authority 服务中都会用到,所以放在server-resource 服务中:
@Configuration
public class TokenStoreAutoConfiguration {
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
public TokenStore tokenStore() {
// 使用redis存储token
RedisTokenStore redisTokenStore = new CustomRedisTokenStore(connectionFactory);
redisTokenStore.setAuthenticationKeyGenerator(new CustomAuthenticationKeyGenerator());
return redisTokenStore;
}
}
/**
* 自定义的RedisTokenStore处理
*/
public class CustomRedisTokenStore extends RedisTokenStore {
public CustomRedisTokenStore(RedisConnectionFactory connectionFactory) {
super(connectionFactory);
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
return super.readAccessToken(tokenValue);
}
@Override
public void removeAccessToken(OAuth2AccessToken accessToken) {
super.removeAccessToken(accessToken);
}
@Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
super.removeAccessTokenUsingRefreshToken(refreshToken);
}
}
public class CustomAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
private static final String RAND = "keyGeneratorRand";
@Override
protected String generateKey(Map<String, String> values) {
// 加入一个随机的要素,保证每次调用时生成的们的hash都不一样
values.put(RAND, UUID.randomUUID().toString());
return super.generateKey(values);
}
}
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hh.config.TokenStoreAutoConfiguration
前面我们说过,资源服务器 server-authority 会和授权服务器 server-resource 存在公共配置类,并把这些公共配置类放在了server-resource 服务中,因此这里需要引入 server-resource 服务的依赖。
数据库我使用了flyway做数据库表的创建和表中数据的更新,因此需要导入flyway工具的依赖。
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>authorityartifactId>
<groupId>com.hhgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>server-authorityartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>com.hhgroupId>
<artifactId>server-resourceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<version>2.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.flywaydbgroupId>
<artifactId>flyway-coreartifactId>
<version>5.2.4version>
dependency>
dependencies>
project>
server:
port: 8081
spring:
# 数据库
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/authority?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
# redis
redis:
database: 0
host: localhost
port: 6379
# 数据库迁移
flyway:
locations: classpath:db/migration
encoding: utf-8
# 日志
logging:
pattern:
console: '%d{2100-01-01 13:14:00.666} [%thread] %-5level %logger{50} - %msg%n'
# mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
在 db/migration/V3.0.0.2__init_table.sql 脚本文件中添加建表语句:
-- 用户表
CREATE TABLE `user`
(
`id` varchar(64) NOT NULL COMMENT '用户id',
`username` varchar(50) NOT NULL COMMENT '账户名称',
`salt` varchar(20) DEFAULT NULL COMMENT '加密盐值',
`password` varchar(200) NOT NULL COMMENT '用户密码密文',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- 角色表
CREATE TABLE `role`
(
`id` varchar(64) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '角色名称',
`description` varchar(300) DEFAULT NULL,
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- 权限表
CREATE TABLE `policy`
(
`id` varchar(32) NOT NULL,
`name` varchar(64) NOT NULL COMMENT '权限策略名',
`createTime` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
ROW_FORMAT = DYNAMIC COMMENT ='权限策略表';
-- 用户角色表
CREATE TABLE `user_role`
(
`id` varchar(64) NOT NULL COMMENT '主键',
`userId` varchar(64) DEFAULT NULL COMMENT '用户id',
`roleId` varchar(64) DEFAULT NULL COMMENT '角色id',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- 角色权限表
CREATE TABLE `role_permission`
(
`id` varchar(64) NOT NULL COMMENT '主键',
`roleId` varchar(64) DEFAULT NULL COMMENT '角色id',
`policyId` varchar(64) DEFAULT NULL COMMENT '菜单权限id',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
@Configuration
public class PasswordEncodeConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
/**
* - @EnableGlobalMethodSecurity:该注解是用来开启权限注解
* - prePostEnabled = true:开启Spring Security提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("root").password(passwordEncoder.encode("123")).roles("ADMIN").build());
return inMemoryUserDetailsManager;
}
//去除security的ROLE_前缀限制
@Bean
public RoleVoter roleVoter(){
RoleVoter roleVoter = new RoleVoter();
roleVoter.setRolePrefix("");
return roleVoter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
// 自定义AuthenticationManager:并没有在工厂中暴露出来
// 使用AuthenticationManagerBuilder来自定义AuthenticationManager,覆盖默认的AuthenticationManager
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
// 开发者如需使用AuthenticationManager,
// 则可以通过覆盖此方法,将configure(AuthenticationManagerBuilder)方法构造的AuthenticationManager暴露为Bean。
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
/**
* 配置授权服务器
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private TokenStore tokenStore;
/**
* 用来配置授权服务器可以为哪些客户端授权,使用哪种授权模式
*
* 密码模式:在密码模式中,用户向客户端提供用户名和密码,客户端通过用户名和密码到认证服务器获取令牌
* (A)用户访问客户端,提供URI连接包含用户名和密码信息给授权服务器
* (B)授权服务器对客户端进行身份验证
* (C)授权通过,返回access_token给客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 客户端id
.withClient("client_id")
// 客户端秘钥
.secret(passwordEncoder.encode("client_secret"))
// 授权模式
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
// access_token的过期时长
.accessTokenValiditySeconds(43200)
// refresh_token的过期时长
.refreshTokenValiditySeconds(86400)
// 允许授权的范围
.scopes("all");
}
/**
* 刷新令牌必须配置userDetailsService,用来刷新令牌时的认证
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 配置token的存储方式
endpoints.tokenStore(tokenStore);
// 配置认证管理器
endpoints.authenticationManager(authenticationManager);
// 配置认证数据源,刷新令牌时的认证
endpoints.userDetailsService(userDetailsService);
}
/**
* 开启token检查接口
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
security.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
}
}
@SpringBootApplication
public class OauthAuthorityApplication {
public static void main(String[] args) {
SpringApplication.run(OauthAuthorityApplication.class,args);
}
}
密码模式:在密码模式中,用户向客户端提供用户名和密码,客户端通过用户名和密码到认证服务器获取令牌:
(A)用户访问客户端,提供URI连接包含用户名和密码信息给授权服务器
(B)授权服务器对客户端进行身份验证
(C)授权通过,返回access_token给客户端
URL格式(不包括参数):http://ip:port/oauth/token ,必填参数如下表:
序号 | 参数 | 描述 |
---|---|---|
1 | grant_type | 授权类型,密码模式:password |
2 | password | 自定义的用户密码,123456 |
3 | resource_ids | 资源id |
4 | client_id | 内存中配置的client |
5 | client_secret | 内存中配置的secret |
测试方式可以使用下面两种方式:
① Postman测试:http://localhost:8081/oauth/token?grant_type=password&client_id=client_id&client_secret=client_secret&username=root&password=123
② Postman测试:http://client:secret@localhost:8080/oauth/token
server:
port: 8082
spring:
# 数据库
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/authority?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
@RestController
@RequestMapping("/api/v1")
public class {
@GetMapping("/hello")
public String hello(){
return "hello spring security";
}
}
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>authorityartifactId>
<groupId>com.hhgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>knowledgeartifactId>
project>
可以直接访问资源:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>authorityartifactId>
<groupId>com.hhgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>knowledgeartifactId>
<dependencies>
<dependency>
<groupId>com.hhgroupId>
<artifactId>server-resourceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
显示未授权:
因为 knowledge 服务中引入了 server-resource 依赖 ,而在这个 starter 中引入了 springsecurity oauth2 的相关依赖,因此所有 controller 层的接口资源都是受保护的,需要到认证服务器获取 token,通过 token 访问受限资源,因此我们需要搭建资源服务器校验 token,下篇文章再说。。。。。。