源码地址
springboot2教程系列
OAuth 概念
OAuth 是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而不需要将用户名和密码提供给第三方应用。OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。
OAuth 2.0 认证流程
第一步:得到授权码 code
首先直接跳转至用户授权地址,即图示 Request User Url ,提示用户进行登录,并给予相关资源授权,得到唯一的 Auth code ,这里注意的是 code 只有 10 分钟的有效期,对于安全考虑,相对于 OAuth 1.0 省了一步获取临时的 Token ,并且有效期也进行了控制,比 1.0 认证简化了很多,并安全一些;
第二步:获取 access token
得到授权 code 后,就是请求 access token ,通过图示 Request access url ,生成得到数据 Token ;
第三步:通过 access token, 获取 OpenID
通过 Access Token 请求 OpenID , OpenID 是用户在此平台的唯一标识,通过图示 Request info url 请求,然后得到 OpenID ;
第四步:通过 access token 及 OpenID 调用 API,获取用户授权信息
通过第二步得到的数据 Token 、第三步得到的 OpenID 及相关 API ,进行请求,获取用户授权资源信息。
OAuth 授权模式
OAuth2.0 定义了 四种授权模式。分别为:
- 授权码模式
- 简化模式
- 密码模式
- 客户端模式
oauth2 实例
可以分为简易的分为三个步骤
- 配置资源服务器
- 配置认证服务器
- 配置spring security
pom文件添加oauth2依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
配置资源服务器
@EnableResourceServer注解来开启资源服务器
@Slf4j
@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint ;
@Bean
public CustomLogoutSuccessHandler customLogoutSuccessHandler(){
return new CustomLogoutSuccessHandler();
} ;
@Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and().requestMatchers().antMatchers("/api/**")
.and().authorizeRequests().antMatchers("/api/**").authenticated();
}
}
自定义401错误码内容
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("Pre-authenticated entry point called. Rejecting access");
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
}
}
配置OAuth2验证服务器
@EnableAuthorizationServer注解开启验证服务器
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm("oauth2-resources") // code授权添加
.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()") // allow check token
.allowFormAuthenticationForClients();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
// 允许 GET、POST 请求获取 token,即访问端点:oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 要使用refresh_token的话,需要额外配置userDetailsService
endpoints.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("demoApp").secret(bCryptPasswordEncoder.encode("demoAppSecret"))
.redirectUris("http://baidu.com")// code授权添加
.authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token")
.scopes("all").resourceIds("oauth2-resource").accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
}
安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestMatchers().antMatchers("/oauth/**", "/login/**", "/logout/**").and().authorizeRequests()
.antMatchers("/oauth/**").authenticated().and().formLogin().permitAll();
}
// 配置内存模式的用户
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("demoUser1").password(this.passwordEncoder().encode("123456"))
.authorities("USER").build());
manager.createUser(User.withUsername("demoUser2").password(this.passwordEncoder().encode("123456"))
.authorities("USER").build());
return manager;
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
}
测试
【密码授权模式-client】
密码模式需要参数:username,password,grant_type,client_id,client_secret
http://localhost:8080/oauth/token?username=demoUser1&password=123456&grant_type=password&client_id=demoApp&client_secret=demoAppSecret
【客户端授权模式-password】
客户端模式需要参数:grant_type,client_id,client_secret
http://localhost:8080/oauth/token?grant_type=client_credentials&client_id=demoApp&client_secret=demoAppSecret
【授权码模式-code】
获取code
http://localhost:8080/oauth/authorize?response_type=code&client_id=demoApp&redirect_uri=http://baidu.com
通过code换token(注意:code参数为授权码模式返回的参数)
http://localhost:8080/oauth/token?grant_type=authorization_code&code=Filepd&client_id=demoApp&client_secret=demoAppSecret&redirect_uri=http://baidu.com
【通过refresh token】 刷新token
http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=7ba47059-d853-4050-9c64-69d0cade71a7&client_id=demoApp&client_secret=demoAppSecret
其中grant_type为固定值:grant_type=refresh_token , refresh_token = 通过code获取的token中的refresh_token
访问:http://127.0.0.1:8080/api/hello/1?access_token=6863bdf3-664b-48b4-9a1e-d983deebfdcc