SSO单点登录之用户认证实践
背景
随着微服务的兴起,单点登录功能呢也随之而来,在登录每个服务的时候,我们应该遵循哪些规则,来认证访问的合法性呢?今天我们来聊聊基于Oauth2的认证规则.(这里就略过添加依赖,准备物料类等步骤我这里直接说明认证部分),
第一步:定义Security配置类
此类的作用是配置认证规则
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 初始化加密对象,此种加密方式是不可逆的,相比于MD5更加安全
* 交给spring管理
* */
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 提供一个负责认证的对象,此对象在基于Oauth2认证时使用
* 交给spring管理
* */
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
重要类:BCryptPasswordEncoder
此类加密是Spring Security 官方推荐的密码解析器,实际应用中也是使用这个加密器居多。BCryptPasswordEncoder是对bcrypt强散列方法的具体实现,是基于Hash算法的单向加密,允许通过Strength参数控制加密强度,默认为10,基于此类进行测试密码加密
package com.qty;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootTest
public class BCryptPasswordEncoderTest {
@Test
void BCPasswordTest(){
// 定义密码
String pwd = "qtycode";
System.out.println("未加密密码:" + pwd);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 通过encode方法对密码进行加密
String encode = passwordEncoder.encode(pwd);
boolean result = passwordEncoder.matches("qtycode", encode);
System.out.println("加密后密码:"+encode);
System.out.println("对比结果:"+ result);
}
}
打印结果
第二步:定义Oauth2认证授权配置
在这里我们首先根据生活中的实际情况考虑一个问题,在生活中什么时候需要认证呢?比如刷卡乘车,是否需要解析你这个卡的用户是谁,余额是否充足等,再比如公司中的CRM系统,是否需要认证你所访问的服务是否有权限等,所以基于实例,Oauth2也有自己的一套认证规则,这套规则规定了认证系统需要哪些对象
1)系统资源–>数据
2)资源拥有者–>用户
3)管理资源服务器
4)对用户进行认证和授权的服务器
5)客户端系统–>负责提交用户的信息
再来分析,对于认证授权系统来说需要构建哪些对象来完成认证
再拿生活中例子说明,比如说,想做一名老师,那么就需要考取教师资格证,那我们去哪考呢?教管局会提供一个报考的入口,我们通过这个入口,填写相应的个人信息,最终审核通过了,就会生成一个准考证,拿着准考证去参加考试,进考场时,还需要有一名监考老师检查你与准考证上的信息是否相符,如果相符就可以参考.
Oauth2认证授权的过程也是如此,
1)提供认证入口:这相当于教资报名的地方
这里的入口指的是一个认证路径:
http://IP地址:端口号/oauth/token
http://IP地址:端口号/oauth/check_token等
2)认证信息:客户端发来请求时所携带什么信息,比如账号密码,相当于报名时填写个人信息
3)负责认证的对象:也就是谁来对客户端的请求进行认证,这相当于监考老师,检查信息是否相符合.
package com.qty.auth.service.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
@Configuration
@EnableAuthorizationServer //在oauth2规范中启动认证和授权
@AllArgsConstructor
public class OauthConfig extends AuthorizationServerConfigurerAdapter {
//@Autowired
private BCryptPasswordEncoder passwordEncoder;
//@Autowired
private AuthenticationManager authenticationManager;
//@Autowired
private TokenStore tokenStore;
//@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
// 提供认证入口
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") // 发布认证入口 /oauth/token
.checkTokenAccess("permitAll()") // 发布检察令牌入口 /oauth/check_token
.allowFormAuthenticationForClients(); // 允许用户以表单形式提交
// super.configure(security);
}
// 客户端请求携带信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//super.configure(clients);
clients.inMemory()
.withClient("gateway-client") // 客户端标识
// 秘钥,这里直接注入此对象即可
.secret(passwordEncoder.encode("123456"))
// 指定认证类型
.authorizedGrantTypes("password", "refresh_token")
.scopes("all"); // 作用域,包含规定的客户端都可进行认证
}
// 负责认证的对象,认证成功颁发令牌,默认令牌是UUID
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//super.configure(endpoints);
// 提供认证对象,此对象已写好,直接注入即可
endpoints.authenticationManager(authenticationManager)
//设置令牌服务,不设置默认是UUID,此次我们使用JWT
.tokenServices(tokenServices())
//设置允许请求方式,默认是post
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
@Bean
public AuthorizationServerTokenServices tokenServices(){
// 生成token对象
DefaultTokenServices ts = new DefaultTokenServices();
// 设置令牌生成方式
ts.setTokenStore(tokenStore);
// 设置令牌增强,此对象还没写,报错没关系
ts.setTokenEnhancer(jwtAccessTokenConverter);
// 令牌有效时长
ts.setAccessTokenValiditySeconds(3600);
// 设置刷新令牌以及时长
ts.setSupportRefreshToken(true);
ts.setRefreshTokenValiditySeconds(3600*24);
return ts;
}
}
第三步:定义认证策略
我们使用JWT规则
package com.qty.auth.service.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class TokenConfig {
/**
* 配置令牌生成策略,oauth2提供了以下几种策略
* 1.JdbcTokenStore:将token储存到关系型数据库,不建议,数据太大的情况效率很低
* 2.RedisTokenStore:将token储存到缓存数据库redis
* 3.JwtTokenStore:将token储存到储存到客户端,token中包含一些用户信息
* 等等
* */
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 设置令牌的创建以及验证方式
* */
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 设置令牌的加密密码
jwtAccessTokenConverter.setSigningKey(SINGING_KEY);
return jwtAccessTokenConverter;
}
private static final String SINGING_KEY = "auth";
}