1. OAuth2 是什么?
OAuth2是目前最流行的授权框架,用来授权第三方应用,获取用户数据。
OAuth2的授权模式
- 授权码模式。最完整和严谨的授权模式,第三方平台登录都是使用的此模式。安全性最高
- 简化模式。省略授权码阶段,客户端是纯静态页面采用此模式。安全性较高
- 密码模式。把用户名密码告诉客户端,对客户端高度信任,比如客户端和认证服务是同一个公司。安全性一般
- 客户端模式。直接以客户端名义申请令牌,很少用。安全性最差
2. 为什么要用OAuth2
在最早的单体架构中,我们使用的是cookie session机制来进行身份的校验的,这种方式既不安全,也存在跨域的问题
随着业务的发展,架构由单体应用发展到了分布式的架构,我们需要一种新的安全的方式来进行身份的校验
- 使用session
但是使用session会存在分布式应用下session的判断问题,此时session我们可以使用redis来存储。 - 基于token
由一台授权服务器进行授权,客户端获取到token后,调用资源服务器的时候,携带上token进行访问即可。
Cookie session 和 token的区别
- cookie是不能跨域的,前后端分离分布式架构实现多系统SSO非常困难
- 移动端应用没有cookie,所以对移动端的支持不好
- token基于header传递,部分解决了CSRF攻击
- token要比sessionId大,客户端存储在Local Storage中,可以直接被JS读取
3. OAuth2的使用
使用OAuth2的时候,需要一个授权服务器-用来办法和验证令牌,其他的访问段称之为资源服务器-需要令牌才能够访问的服务。
1. 依赖引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
2. 授权服务器配置
需要配置一个OAuths的配置类,需要继承AuthorizationServerConfigurerAdapter
代码如下:
@Configuration
@EnableAuthorizationServer //开启授权配置服务类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
// 客户端的id
private static final String CLIENT_ID = "客户端的id";
// 是否对信息进行加密 {noop}secret就是不需要
private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
// 范围。是全部还是只读,只写
private static final String ALL = "all";
// 有效期
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*60;
// 密码模式授权模式
private static final String GRANT_TYPE_PASSWORD = "password";
//授权码模式
private static final String AUTHORIZATION_CODE = "authorization_code";
//简化授权模式
private static final String IMPLICIT = "implicit";
//客户端模式
private static final String CLIENT_CREDENTIALS="client_credentials";
// 客户端配置信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(CLIENT_ID)
.secret(SECRET_CHAR_SEQUENCE)
.autoApprove(false)
.redirectUris("重定向uri") //重定向uri
.scopes(ALL)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.authorizedGrantTypes(AUTHORIZATION_CODE, IMPLICIT, GRANT_TYPE_PASSWORD, CLIENT_CREDENTIALS);
}
// 令牌的存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore());
}
/**
* 认证服务器的安全配置
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 开启/oauth/check_token验证端口认证权限访问,checkTokenAccess("isAuthenticated()")设置授权访问
.checkTokenAccess("permitAll()")
//允许表单认证
.allowFormAuthenticationForClients();
}
@Bean
public TokenStore memoryTokenStore() {
return new InMemoryTokenStore();
}
}
配置Spring Security
,需要继承WebSecurityConfigurerAdapter
类,实现Spring Security
的权限配置
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 设置用户角色数据内存储存,这里可以修改为从数据库获取对应的用户信息并进行校验
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //auth.inMemoryAuthentication()
auth.inMemoryAuthentication()
.withUser("用户")
.password("{noop}密码") //使用springsecurity5,需要加上{noop}指定使用NoOpPasswordEncoder给DelegatingPasswordEncoder去校验密码
.roles("角色");
}
// 需要放行的资源
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
// web.ignoring().antMatchers("/asserts/**");
}
// 授权的全局资源
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().permitAll()
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
.and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**", "/api/**").permitAll()
.anyRequest().authenticated()
// 关闭跨域保护;
.and().csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
配置好以后就可以使用http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code
来测试一下看是否可以获取到token,这个的respone_type=code说明它是验证码模式
3. 资源服务器的配置
1. 需要引入两个依赖,和授权服务器一样
2. 配置类配置
Oauth2ResourceServerConfiguration
继承ResourceServerConfigurerAdapter
代码如下
@Configuration
@EnableResourceServer
public class Oauth2ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
// 进行token校验的地址
private static final String CHECK_TOKEN_URL = "http://localhost:8888/oauth/check_token";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL);
tokenService.setClientId("授权的客户端id");
tokenService.setClientSecret("授权的客户端密码");
resources.tokenServices(tokenService);
}
}
SecurityConfiguration
继承WebSecurityConfigurerAdapter
代码如下:
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 可以在这里自定义需要放行的接口
http.authorizeRequests().antMatchers("/**").authenticated();
// 禁用CSRF
http.csrf().disable();
}
}
接下来去直接访问资源服务器就会发现已经进行了权限校验了,此时可以携带token去访问,是可以访问成功的。