基于Security Oauth实现单点登录
前一段时间业务需要用到单点登录于是在网上看了各种博客,但是各种都是无头无尾的,最后只好看着官网一步步弄了一个,现在分享一些心得希望能帮到大家
注意 :这是一个example 为了追求精简,很多地方的写法非常不恰当 比如 直接new 对象这种方式在实际的开发中显然是不恰当,这篇文章需要有security的基础.如果没有请看我的上一篇文章.
基本概念
OAuth2.0 : 一种标准开放的认证协议,细致入微的描述请点击百度百科。
认证服务器 : 也叫授权服务器,生成,校验用户信息 。
资源服务器 : 被保护的资源,具体的资源。例如订单服务就是一个资源服务,不可能让谁都能访问订单。
认证流程
认证模式一共有4种,这里写最常用的两种。
授权码模式 :举例拿 “使用QQ登录CSDN“,用户点击qq登录(发起资源请求)跳转到qq登录,输入正确的用户密码(输入正确),页面提示用户是否授权给CSDN,同意后 返回一个一次性临时的code,浏览器通过这个code去qq认证服务器申请token,校验成功后qq服务器返回token,CSDN服务器拿到token 获取用户信息,认证通过。特点: 安全系数高,token值不经过浏览器。
密码模式: 输入正确的用户信息,直接去认证服务器认证 申请token,然后通过token直接访问对应资源请求。特点:客户端会直接获取到你的token,有泄漏的风险。
mavn依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.3.RELEASE</version>
</dependency>
认证服务器配置
配置自定义security的适配器
@Configuration
@EnableWebSecurity
@EnableAuthorizationServer //认证服务器
@EnableGlobalMethodSecurity(prePostEnabled=true) //开启权限注解
public class SpringWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//token仓库
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
//密码处理
@Bean
public PasswordEncoder getPasswordEncoder() {
PasswordEncoder passwordEncoder = new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.equals(s);
}
};
return passwordEncoder;
}
@Autowired
private SpringUserDetailsService springUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//自定义用户详情初始化
//此处我省略这个类的编写,实在想看请看上一个博客
auth.userDetailsService(springUserDetailsService).passwordEncoder(getPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//除去登录登出,其他请求一律认证
http.authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().logout().permitAll()
.and().csrf().disable();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
配置类AuthorizationServerConfigurerAdapter(授权服务器适配器)
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
ClientDetailsService clientDetailsService;
private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
private TokenStore tokenStore = new InMemoryTokenStore();
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置客户端
clients
.inMemory() //存储策略 基于内存 (不建议使用)
.withClient("client") //客户端ID
.secret("123456")//客户端密钥
.resourceIds("hi") //客户端具备那些资源服务器授权
.authorizedGrantTypes("authorization_code","password","refresh_token") //可选的 认准方式
.redirectUris("www.baidu.com") //使用授权码验证时,获取code跳转地址
.scopes("read"); //授权的作用域 可看似为权限
// clients.withClientDetails() 也可以设置你要放入的自定义ClientDetails 对象 功能和用法和 UserDetails 类似
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager) //认证管理 这个是来自security适配器默认提供的
.allowedTokenEndpointRequestMethods(HttpMethod.POST) //允许POST提交
.authorizationCodeServices(authorizationCodeServices) //授权码服务设置
.tokenServices(getAuthorizationServerTokenServices()) //token服务 设置
;
}
//令牌管理服务
@Bean
public AuthorizationServerTokenServices getAuthorizationServerTokenServices(){
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setClientDetailsService(clientDetailsService); // 配置客户端详情
defaultTokenServices.setTokenStore(tokenStore); //设置存储方式
defaultTokenServices.setSupportRefreshToken(true); // 支持刷新策略
return defaultTokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer
.allowFormAuthenticationForClients() //允许POST请求
.checkTokenAccess("permitAll()") //校验令牌合法性端点完全公开
.tokenKeyAccess("permitAll()"); //公钥端点完全公开
}
}
Oauth自带默认接口
授权服务器
- /oauth/check_token 用于其他服务器远程校验token合合法性,以及获取token对应的用户信息
- /oauth/token 申请token值,当grant_type 不同时,申请的参数也不一样.
- /oauth/authorize 当使用授权码方式时,获取code
资源服务器配置
ResourceServerConfigurerAdapter 配置自定义的资源服务器适配器
@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class AuthorizationConfig extends ResourceServerConfigurerAdapter {
private final String RID= "hi"; //定义的资源服务器的id
@Autowired
RemoteTokenServices remoteTokenServices;
@Override
public void configure(ResourceServerSecurityConfigurer rsc){
rsc
.resourceId(RID) //资源服务器Id
.tokenServices(remoteTokenServices) //认证token
.tokenStore(new InMemoryTokenStore()) //存储策略
.stateless(true);
}
@Override
public void configure(HttpSecurity hs) throws Exception {
hs.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("http://localhost:8080/login").permitAll();
}
@Bean
public RemoteTokenServices checkToken(){
RemoteTokenServices rts = new RemoteTokenServices(); //远程调用授权服务器的认证token
rts.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
rts.setClientId("client");
rts.setClientSecret("123456");
// DefaultTokenServices dts = new DefaultTokenServices(); //本地验证token
return rts;
}
}
测试
密码模式获取token:
client_id 和 client_secret 都要求在认证服务器有对应的客户端. 用户名和密码 只要能过UserDetails即可.要求就是必须能通过用户认证和客户端认证,且客户端还有对应资源服务器的id
grant_type :该参数用来声明是使用了那种参数来请求token 这里写的是password
授权码模式获取token
先得获取code,根据code获取token
请求获取code地址
http://localhost:8080/oauth/authorize?client_id=client&response_type=code
这个要填写 client_id 和 response_type 会给你跳转到登录页面
输入正确之后
获取code (这个自动跳转的页面我没有设置故404)
请求 /oauth/token 附带适配器里面的参数即可获取token
使用token
将返回结果中的 token_type + 空格 + access_token 填写成请求资源服务请求头中Authorization 的Value即可访问