Spring Cloud OAuth2 是 Spring Cloud 体系对OAuth2协议的实现,可以⽤来做多个微服务的统⼀认证(验证身份合法性)授权(验证权限)。通过向OAuth2服务(统⼀认证授权服务)发送某个类型的grant_type进⾏集中认证和授权,从而获得access_token(访问令牌),而这个token是受其他微服务信任的
- 基于Session的认证方式
在分布式的环境下,基于session的认证会出现⼀个问题,每个应⽤服务都需要在session中存储用
户身份信息,通过负载均衡将本地的请求分配到另⼀个应⽤服务需要将session信息带过去,否则
会重新认证。我们可以使用Session共享、Session黏贴等⽅案
- 基于token的认证方式
基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任
意地⽅,并且可以实现web和app统⼀认证机制。其缺点也很明显,token由于自包含信息,因此
⼀般数据量较大,而且每次请求 都需要传递,因此比较占带宽。另外,token的签名验签操作也会
给cpu带来额外的处理负担
OAuth2介绍
OAuth(开放授权)是⼀个开放协议/标准,允许用户授权第三⽅应⽤访问他们存储在另外的服务提供者上的信息,而不需要将⽤户名和密码提供给第三方应用或分享他们数据的所有内容
- 资源所有者(Resource Owner):可以理解为用户自己
- 客户端(Client):我们想登陆的网站或应用,比如拉勾网
- 认证服务器(Authorization Server):可以理解为微信或者QQ
- 资源服务器(Resource Server):可以理解为微信或者QQ
OAuth2的颁发Token授权方式
- 授权码(authorization-code)
- 密码式(password)提供⽤户名+密码换取token令牌
- 隐藏式(implicit)
- 客户端凭证(client credentials)
搭建认证服务器(Authorization Server)
认证服务器(Authorization Server),负责颁发token
- 在m-parent的基础上新建项目m-cloud-oauth-server-9999
- pom.xml
<!--导⼊Eureka Client依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--导⼊spring cloud oauth2依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
- application.yml(构建认证服务器,配置⽂件无特别之处)
server:
port: 9999
spring:
application:
name: m-cloud-oauth-server
#注册发现
eureka:
client:
service-url:
defaultZone: http://CloudEurekaServerA:8761/eureka,http://CloudEurekaServerB:8762/eureka
instance:
prefer-ip-address: true
instance-id: ${
spring.cloud.client.ip-address}:${
spring.application.name}:${
server.port}:@project.version@
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableAuthorizationServer //开启认证服务器功能
public class MCloudOuathServer9999 {
public static void main(String[] args) {
SpringApplication.run(MCloudOuathServer9999.class, args);
}
}
- 配置类
- 认证服务器配置类
package com.w.edu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@Configuration
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 认证服务器最终以api接口方式对外提供服务(生成令牌、校验令牌)
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
security
//允许客户端表单认证
.allowFormAuthenticationForClients()
//开启端口/oauth/token_key的访问权限
.tokenKeyAccess("permitAll()")
//开启端口/oauth/check_token的访问权限
.checkTokenAccess("permitAll()");
}
/**
* 客户端吧详情配置
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
//客户端信息存储在什么地方,既可以在内存也可以在数据库里
clients.inMemory()
//添加客户端配置。指定client_id
.withClient("client_w")
//指定客户端密码、安全码
.secret("abcdef")
//指定客户端可访问的资源id 清单
.resourceIds("autodeliver")
//认证类型、客户端令牌颁发模式//认证模式。可以配置多个
.authorizedGrantTypes("password", "refresh_token")
//客户端权限范围,配置all 即可
.scopes("all");
}
/**
* Token 令牌管理相关
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints
//token 存储方式
.tokenStore(tokenStore())
//配置token细节
.tokenServices(authorizationServerTokenServices())
//认证管理器
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
/**
* 获取Token服务对象,描述token有效期等信息
*
* @return
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//开启令牌刷新
defaultTokenServices.setSupportRefreshToken(Boolean.TRUE);
//
defaultTokenServices.setTokenStore(tokenStore());
//设置令牌有效时间(一半2小时)
defaultTokenServices.setAccessTokenValiditySeconds(20);
//设置令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
return defaultTokenServices;
}
}
- 安全配置类
package com.w.edu.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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
/**
* @author Mrwg
* @date 2023/4/13 20:30
* @description
* 处理用户名和密码的校验
*/
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
/**
*注入AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 处理用户名密码事宜
* 1 客户传递username 和password 参数到认证服务器
* 2一半存储在数据表中
* 3 根据用户传递的数据,验证当前传递过来用户信息的合法性
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 实例化用户对象
UserDetails user = new User("admin","123456",new ArrayList<>());
auth.inMemoryAuthentication()
.withUser(user).passwordEncoder( passwordEncoder());
}
}
- 测试
- 获取token
http://localhost:9999/oauth/token?client_secret=abcdef&grant_type=password&username=admin&password=123456&client_id=client_w- endpoint:/oauth/token
- 获取token携带的参数
client_id:客户端id
client_secret:客户单密码
grant_type:指定使用哪种颁发类型,password
username:用户名
password:密码
- 校验token
http://localhost:9999/oauth/check_token?token=83964ab2-9b37-4c8f-8f4c-1c37a496b902
- 刷新token
http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client_w&client_secret=abcdef&refresh_token=06c1da97-452f-41ff-a597-89892e43ee49
搭建资源服务器(Resource Server)
- 改造
m-service-autodeliver-8092
服务 - 依赖
添加认证服务器OAuth2依赖,到m-service-autodeliver-8092
服务 - application.yml
新增配置
oauth2:
server:
check-token-url: http://localhost:9999/check_token
- 新增测试controller
@RestController
@RequestMapping("/demo")
public class DemoController {
/**
* 使用Feign
*
* @param userId
* @return
*/
@GetMapping("/test")
public String findResumeOpenState() {
return "demo/test";
}
}
@RestController
@RequestMapping("/other")
public class OtherController {
/**
* 使用Feign
*
* @param userId
* @return
*/
@GetMapping("/test")
public String findResumeOpenState() {
return "other/test";
}
}
- 启动服务,并验证
- 需要认证无权限访问接口
- 无要认证有权限访问接口
- 携带令牌访问
- 获取令牌
- 访问接口
- 获取令牌
思考
思考:当我们第⼀次登陆之后,认证服务器颁发token并将其存储在认证服务器中,后期我们访问资源服务器时会携带token,资源服务器会请求认证服务器验证token有效性,如果资源服务器有很多,那么认证服务器压力会很大…
使用 JWT 进行改造,使用JWT机制之后资源服务器不需要访问认证服务器…