OAuth2 协议:认证服务器允许⽤户授权第三⽅应⽤访问他们存储在另外的服务提供者(用户服务)上的信息,⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容
Spring Cloud OAuth2 是 Spring Cloud 体系对OAuth2协议的实现,可以⽤来做多个微服务的统⼀认证(验证身份合法性)授权(验证权限)。通过向OAuth2服务(统⼀认证授权服务)发送某个类型的grant_type进⾏集中认证和授权,从⽽获得access_token(访问令牌),⽽这个token是受其他微服务信任的,携带这个token可以访问其他微服务的资源
demo
创建父项目
父项目pom文件
<groupId>chris-cloud-oauth2-parent</groupId>
<artifactId>oauth2-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
创建子项目
注册中心搭建
eureka服务目录
启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
application文件
server:
port: 8761
spring:
application:
name: oauth-eureka-eureka
eureka:
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
pom文件
<artifactId>oauth-eureka</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>chris-cloud-oauth2-parent</groupId>
<artifactId>oauth2-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!--Eureka server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--修复gson版本问题-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
认证服务搭建
创建服务器认证配置OauthServerConfiger
@Configuration
@EnableAuthorizationServer // 开启认证服务器功能
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired //我在SecurityConfiger中注入
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private MyAccessTokenConvertor myAccessTokenConvertor;
@Autowired //我在SecurityConfiger中注入
private PasswordEncoder passwordEncoder;
/**
* 开启接口的访问权限
*/
@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.withClientDetails(createJdbcClientDetailsService());
}
@Bean
public JdbcClientDetailsService createJdbcClientDetailsService() {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
return jdbcClientDetailsService;
}
/**
* token的配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints
.tokenStore(tokenStore()) // 指定token的存储方法
.tokenServices(authorizationServerTokenServices()) // token细节
.authenticationManager(authenticationManager) // 指定认证管理器
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
.accessTokenConverter(myAccessTokenConvertor);
}
public TokenStore tokenStore(){
//token放在redis
return new RedisTokenStore(redisConnectionFactory);
}
/**
* 获取一个token服务对象
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
// 使用默认实现
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
// 是否开启token刷新
defaultTokenServices.setSupportRefreshToken(true);
//token的储存方式
defaultTokenServices.setTokenStore(tokenStore());
// 设置令牌有效时间
defaultTokenServices.setAccessTokenValiditySeconds(30000);//30秒
// 设置刷新令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
return defaultTokenServices;
}
}
创建用户认证配置SecurityConfiger
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired //用户服务
private JdbcUserDetailsService jdbcUserDetailsService;
/**
* 注入一个认证管理器对象到容器
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 注入密码编码对象
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
//密码不进行加密处理
return NoOpPasswordEncoder.getInstance();
}
/**
* 验证用户名和密码
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从数据库中关联用户
auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
}
}
创建JdbcUserDetailsService
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UsersRepository usersRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//Users类实现了UserDetails
Users users = usersRepository.findByUsername(username);
//返回用户名,密码,权限集合
return users;
}
}
创建UsersRepository,我使用的是JPA
public interface UsersRepository extends JpaRepository<Users,Long> {
Users findByUsername(String username);
}
创建Users类
//数据库中的表名
@Table(name = "user")
@Entity
public class Users implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String username;
private String password;
//自定义的属性
private String others;
public String getOthers() {
return others;
}
public void setOthers(String others) {
this.others = others;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
注入令牌转换器
@Component
public class MyAccessTokenConvertor extends DefaultAccessTokenConverter {
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map<String, Object> stringMap = (Map<String, Object>)super.convertAccessToken(token, authentication);
//获得用户信息
Object principal = authentication.getPrincipal();
//把用户信息放到map中
stringMap.put("context", principal);
return stringMap;
}
}
启动类
@SpringBootApplication
@EnableDiscoveryClient
@EntityScan("com.chris.oauth.pojo")
public class OauthServerApplication {
public static void main(String[] args) {
SpringApplication.run(OauthServerApplication.class,args);
}
}
Pom文件
<groupId>chris-cloud-oauth2</groupId>
<artifactId>oauth-server</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>chris-cloud-oauth2-parent</groupId>
<artifactId>oauth2-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!--导⼊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>
<!--导⼊jpa依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8888
spring:
application:
name: chris-oauth-server
datasource:
url: jdbc:mysql://127.0.0.1:3306/{数据库名}?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: 用户名
password: 密码
redis:
password: 密码
host: redis地址
port: 6380
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
prefer-ip-address: true
#⾃定义实例显示格式
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
数据库表
客户端资源表oauth_client_details
ddl:
CREATE TABLE oauth_client_details
(
client_id
varchar(255) NOT NULL,
resource_ids
varchar(255) DEFAULT NULL,
client_secret
varchar(255) DEFAULT NULL,
scope
varchar(255) DEFAULT NULL,
authorized_grant_types
varchar(255) DEFAULT NULL,
web_server_redirect_uri
varchar(255) DEFAULT NULL,
authorities
varchar(255) DEFAULT NULL,
access_token_validity
int DEFAULT NULL,
refresh_token_validity
int DEFAULT NULL,
additional_information
text,
create_time
timestamp NULL DEFAULT CURRENT_TIMESTAMP,
archived
tinyint(1) DEFAULT ‘0’,
trusted
tinyint(1) DEFAULT ‘0’,
autoapprove
varchar(255) DEFAULT ‘false’,
PRIMARY KEY (client_id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
用户表user
ddl省略
启动eureka和oauth服务,调用http://localhost:8888/oauth/token获取token测试
在自己写的login接口中可以转发到这个接口来获取token然后返回给用户
资源服务器
创建资源服务配置ResourceServerConfiger
@Configuration
@EnableResourceServer // 开启资源服务器功能
@EnableWebSecurity
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
@Autowired
private MyAccessTokenConvertor myAccessTokenConvertor;
/**
* 该方法用于定义资源服务器向远程认证服务器发起请求,进行token校验
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 设置当前资源服务的资源id
resources.resourceId("sourceServer");
// 定义token服务对象(token校验就应该靠token服务对象)
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
// 校验端点/接口设置,可以写在配置文件中
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8888/oauth/check_token");
// 携带客户端id和客户端密码
remoteTokenServices.setClientId("chris_client");
remoteTokenServices.setClientSecret("123456");
remoteTokenServices.setAccessTokenConverter(myAccessTokenConvertor);
resources.tokenServices(remoteTokenServices);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 设置session的创建策略
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/check/**").authenticated() // check为前缀的请求需要token
.anyRequest().permitAll(); // 其他请求不认证
}
}
创建token转换器MyAccessTokenConvertor
@Component
public class MyAccessTokenConvertor extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
//获得在认证服务中放入context的对象
oAuth2Authentication.setDetails(map.get("context")); // 将context放入认证对象中
return oAuth2Authentication;
}
}
创建一个controller
@RestController
public class SourceController {
@GetMapping
@RequestMapping("check/testFunction")
public String testFunction(){
//从上下文获得前面MyAccessTokenConvertor放入detail的用户信息,可以自己写个工具类
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)SecurityContextHolder.getContext().getAuthentication().getDetails();
Object decodedDetails = details.getDecodedDetails();
System.out.println(decodedDetails);
return "testFunctionReturn";
}
}
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class SourceApplication {
public static void main(String[] args) {
SpringApplication.run(SourceApplication.class, args);
}
}
pom文件
<parent>
<artifactId>oauth2-parent</artifactId>
<groupId>chris-cloud-oauth2-parent</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>source-server</artifactId>
<dependencies>
<!--导⼊Eureka Client依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
</dependencies>
application.yml
server:
port: 8090
spring:
application:
name: chris-source-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
不带token调用测试接口
带token调用接口