权限认证(三):OAuth2认证服务器
- 创建父工程mengxuegu-cloud-oauth2-parent
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<properties>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<mybatis-plus.version>3.2.0</mybatis-plus.version>
<druid.version>1.1.12</druid.version>
<kaptcha.version>2.3.2</kaptcha.version>
<fastjson.version>1.2.8</fastjson.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-collections.version>3.2.2</commons-collections.version>
<commons-io.version>2.6</commons-io.version>
<!-- 定义版本号, 子模块直接引用-->
<mengxuegu-security.version>1.0-SNAPSHOT</mengxuegu-security.version>
</properties>
<!--依赖声明-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<!--maven不支持多继承,使用import来依赖管理配置-->
<scope>import</scope>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- kaptcha 用于图形验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<!-- 工具类依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!--springboot 打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 创建子模块mengxuegu-cloud-oauth2-base
<dependencies>
<!--类中setter/getter,使用注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 工具类依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
- resource路径下编写logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--梦学谷 www.mengxuegu.com -->
<configuration>
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="stdout" />
</root>
</configuration>
- 在com.mengxuegu.base.result路径下编写MengxueguResult
@Data
public class MengxueguResult implements Serializable {
// 响应业务状态
private Integer code;
// 响应消息
private String message;
// 响应中的数据
private Object data;
public MengxueguResult() {
}
public MengxueguResult(Object data) {
this.code = 200;
this.message = "OK";
this.data = data;
}
public MengxueguResult(String message, Object data) {
this.code = 200;
this.message = message;
this.data = data;
}
public MengxueguResult(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public static MengxueguResult ok() {
return new MengxueguResult(null);
}
public static MengxueguResult ok(String message) {
return new MengxueguResult(message, null);
}
public static MengxueguResult ok(Object data) {
return new MengxueguResult(data);
}
public static MengxueguResult ok(String message, Object data) {
return new MengxueguResult(message, data);
}
public static MengxueguResult build(Integer code, String message) {
return new MengxueguResult(code, message, null);
}
public static MengxueguResult build(Integer code, String message, Object data) {
return new MengxueguResult(code, message, data);
}
public String toJsonString() {
return JSON.toJSONString(this);
}
/**
* JSON字符串转成 MengxueguResult 对象
* @param json
* @return
*/
public static MengxueguResult format(String json) {
try {
return JSON.parseObject(json, MengxueguResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 创建子模块mengxuegu-cloud-oauth2-server
<dependencies>
<dependency>
<groupId>com.mengxuegu</groupId>
<artifactId>mengxuegu-cloud-oauth2-base</artifactId>
<version>${mengxuegu-security.version}</version>
</dependency>
<!--spring mvc相关的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security、OAuth2 和JWT等 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- springboot 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--热部署 ctrl+f9-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--启动类-->
<configuration>
<mainClass>com.mengxuegu.oauth2.AuthServerApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
- 编写application.yml
server:
port: 8090
servlet:
context-path: /auth # 应用名 localhost:8090/auth
- com.mengxuegu.oauth2路径下编写启动类
@SpringBootApplication
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
}
- 编写配置类
@Configuration
@EnableAuthorizationServer // 开启了认证服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 配置被允许访问此认证服务器的客户端信息
* 1.内存方式
* 2. 数据库方式
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 内存方式管理客户端信息
clients.inMemory()
.withClient("mengxuegu-pc") // 客户端id
.secret(passwordEncoder.encode("mengxuegu-secret")) // 客户端密码,需加密
.resourceIds("product-server") // 资源id,可理解为微服务名称,表示可访问哪些微服务
.authorizedGrantTypes("authorization_code", "password", "implicit", "client_credentials", "refresh_token") //认证模式
.scopes("all") // 授权范围标识,哪部分资源可访问(all只是标识,不是说所有资源)
.autoApprove(false) // false 跳到一个授权页面手动点击授权,true不需要手动点授权,直接响应一个授权码
.redirectUris("http://www.mengxuegu.com/") // 客户端回调地址
;
}
}
@Configuration
public class SpringSecurityBean {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder.encode("1234"))
.authorities("product");
}
}
- 测试
# 浏览器访问
http://localhost:8090/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=code
# 进入用户认证页面,输入用户名和密码
# 选择将哪些资源开放给第三方客户端(之前在配置类中配置了autoApprove(false)表示手动选择要开放的资源)
- 获取授权码
http://localhost:8090/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=code
- 密码模式
# SpringSecurityConfig类中添加如下
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
# AuthorizationServerConfig类中添加如下
@Autowired // SpringSecurityConfig添加到容器中了
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 密码模式要设置认证管理器
endpoints.authenticationManager(authenticationManager);
}
-
postman测试
-
简单模式
# 浏览器访问
http://localhost:8090/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=token
-
客户端模式
-
使用redis管理访问令牌
# 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# 编写TokenConfig
@Configuration
public class TokenConfig {
@Autowired //采用redis管理token
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore() {
// redis 管理令牌
return new RedisTokenStore(redisConnectionFactory);
}
}
# AuthorizationServerConfig配置如下
@Autowired // token管理方式,在TokenConfig类中已对添加到容器中了
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 令牌的管理方式
endpoints.tokenStore(tokenStore);
}
- 本地启动redis测试
- 由于我本地没有安装redis
# yml中配置如下
spring:
redis:
database: 0
host: 124.222.5.107
port: 6379
password: 123456
# client-type: lettuce
# lettuce:
# pool:
# max-active: 8
# max-wait: -1ms
# max-idle: 8
# min-idle: 0
timeout: 2000
- 启动报错
Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 124.222.5.107:6379
---分割线
使用redis存储访问令牌
参考
# pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
# yml
spring:
application:
name: security-demo
redis:
host: 192.168.1.102
port: 6379
# password: 没有不用写
lettuce:
# 连接池配置
pool:
# 连接池中的最小空闲连接,默认 0
min-idle: 0
# 连接池中的最大空闲连接,默认 8
max-idle: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制),默认 -1ms
max-wait: -1ms
# 连接池最大连接数(使用负值表示没有限制),默认 8
max-active: 8
# 编写TokenConfig
@Configuration
public class TokenConfig {
@Autowired //采用redis管理token
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore() {
// redis 管理令牌
return new RedisTokenStore(redisConnectionFactory);
}
}
# AuthorizationServerConfig配置如下
@Autowired // token管理方式,在TokenConfig类中已对添加到容器中了
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 令牌的管理方式
endpoints.tokenStore(tokenStore);
}
- 测试