前言:上一章Oauth2 详解介绍了Oauth2是干什么的,使用场景,运行原理以及授权模式。
这一章我们主要以密码模式举例
密码模式:
第一步:用户访问用页面时,输入第三方认证所需要的信息(QQ/微信账号密码)
第二步:应用页面那种这个信息去认证服务器授权
第三步:认证服务器授权通过,拿到token,访问真正的资源页面
一、创建项目添加依赖
创建Springboot Web项目 添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.sl</groupId>
<artifactId>spring-boot-security-oauth2</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在依赖中添加了redis,因为redis有过期功能,很适合令牌存储。
二、添加application.properties
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
三、配置授权服务器
授权范围器和资源服务器可以是同一台服务器,也可是不同服务器,这里是同一台服务器
/**
* @author shuliangzhao
* @Title: AuthorizationServerConfig
* @ProjectName spring-boot-learn
* @Description: TODO
* @date 2019/9/4 20:24
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("password")
.authorizedGrantTypes("password","refresh_token")//表示授权模式支持password和refresh_token
.accessTokenValiditySeconds(1800)
.resourceIds("rid")//配置资源id
.scopes("all")
.secret("$2a$10$yjMPY5kUmnK2YRGt5zeaD.eaPHa7.wYxgLPb9pzmJBzDi1spupgty");//配置加密后的密码
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//表示支持client_id和client_secret
security.allowFormAuthenticationForClients();
}
public static void main(String[] args) {
String encode = new BCryptPasswordEncoder().encode("123");
System.out.println(encode);
}
}
自定义类AuthorizationServerConfig 继承AuthorizationServerConfigurerAdapter ,完成对授权服务器的配置,然后通过直接@EnableAuthorizationServer开发授权服务器。
四、配置资源服务器
资源服务器
/**
* @author shuliangzhao
* @Title: ResourceServer
* @ProjectName spring-boot-learn
* @Description: TODO
* @date 2019/9/4 20:37
*/
@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter{
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//配置资源id,这里的资源id和授权服务器的资源id一致。资源仅基于令牌认证
resources.resourceId("rid").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("admin")
.antMatchers("/user/**")
.hasRole("user")
.anyRequest().authenticated();
}
}
自定义类ResourceServer 继承ResourceServerConfigurerAdapter,添加注解EnableResourceServer开启资源服务器
五、配置Spring Security
/**
* @author shuliangzhao
* @Title: WebSecurityConfig
* @ProjectName spring-boot-learn
* @Description: TODO
* @date 2019/9/4 20:41
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$yjMPY5kUmnK2YRGt5zeaD.eaPHa7.wYxgLPb9pzmJBzDi1spupgty")
.roles("admin")
.and()
.withUser("zhao")
.password("$2a$10$yjMPY5kUmnK2YRGt5zeaD.eaPHa7.wYxgLPb9pzmJBzDi1spupgty")
.roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**")
.authorizeRequests()
.antMatchers("/oauth/**")
.permitAll()
.and().csrf().disable();
}
}
敲黑板:Spring security配置和资源服务器配置中,一共涉及了两个HttpSecurity,其中Spring Security中的配置优先级高于资源服务器的配置,即请求地址先经过SpringSecurity的HttSecurity
五、测试验证
@RestController
public class Oauth2Controller {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
所有配置完成后我们启动项目,授权发送一个post请求获取token
http://localhost:8080/oauth/token?username=zhao&password=123&grant_type=password&client_id=password&scope=all&client_secret=123
请求地址中参数包括用户名,密码,授权模式,客户端id,scope以及客户端密码。返回信息:
{
"access_token": "624df6db-637c-4094-a4ac-f34b30c8e170",
"token_type": "bearer",
"refresh_token": "df8093e4-0c20-4157-8f1c-f0d071c75dff",
"expires_in": 1668,
"scope": "all"
}
返回结果又access_token,token_type,refresh_token,expires_in以及scope,其中access_token是获取其它资源的令牌。refresh_tokn是刷新令牌,expires_in是过期时间。
刷新token的链接:
http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=df8093e4-0c20-4157-8f1c-f0d071c75dff&client_id=password&client_secret=123
返回结果:
{
"access_token": "9ff75b32-ece9-479f-bfea-c8deb42c172f",
"token_type": "bearer",
"refresh_token": "df8093e4-0c20-4157-8f1c-f0d071c75dff",
"expires_in": 1799,
"scope": "all"
}
获取访问资源
http://localhost:8080/user/hello?access_token=9ff75b32-ece9-479f-bfea-c8deb42c172f
返回结果
hello user