前言添加引用先获取一下Redis的配置创建一个TokenGranterConfig,重新配置一下授权设置继承AbstractTokenGranter ,实现自定义的密码认证实现UserDetailsService 获取用户信息的具体逻辑继承AuthorizationServerConfigurerAdapter 进行授权/认证服务器的配置继承WebSecurityConfigurerAdapter类,复写方法实现自定义安全访问策略关于服务端,我们也要配置Oauth2,拦截请求必须带有认证信息且认证有效才能访问接口最后
前言微服务中使用Oauth2做授权认证,想要实现以下几点
1、单点登录,所以首先要将认证信息都存储在redis中
2、针对用户名密码方式获取授权,添加更多的细节操作,下面实现的细节只是一些简单的例子
先获取一下Redis的配置org.springframework.cloud >spring-cloud-starter-oauth2org.springframework.boot >spring-boot-starter-data-redis
简单配置一下redis
custom:
datasource:
redis:
ip: 127.0.0.1
port: 6379
smsExpire: 60000
@Data
@ConfigurationProperties(prefix = "custom.datasource.redis")
public class RedisProperties {
private String ip;
private int port;
private int smsExpire;
}
创建一个TokenGranterConfig,重新配置一下授权设置
@Configuration
public class TokenGranterConfig {
//客户端认证
//在后面的相关配置中 配置了从数据库中读取,也可以存在内存中 InMemery
//在后面的AuthorizationServerConfig中有相关配置 数据库默认 client_id:app client_secret:加密(app)
//标志在调用 oauth/token 获取授权时,前端需要传递 client_id:app 和 client_secret:app
@Autowired
private ClientDetailsService clientDetailsService;
//Token授权方式
private TokenGranter tokenGranter;
//Token存储
@Autowired
private TokenStore tokenStore;
// 认证管理器 用于处理一个认证请求,也就是Spring Security中的Authentication认证令牌。
@Autowired
private AuthenticationManager authenticationManager;
private AuthorizationServerTokenServices tokenServices;
private boolean reuseRefreshToken = true;
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private UserDetailsService userDetailsService;
//注册 TokenGranter的Bean,后面在配置授权认证服务器时候会注入这个Bean
@Bean
public TokenGranter tokenGranter(){
if(null == tokenGranter){
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if(delegate == null){
//返回一个复合的认证机制
delegate = new CompositeTokenGranter(getDefaultTokenGranters());
}
return delegate.grant(grantType,tokenRequest);
}
};
}
return tokenGranter;
}
//支持的授权模式
private List getDefaultTokenGranters() {
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List tokenGranters = new ArrayList();
//四种默认的授权模式
//授权码模式
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
//refresh模式
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
//简化模式
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory);
tokenGranters.add(implicit);
//客户端模式
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
if (authenticationManager != null) {
//自定义的密码模式
tokenGranters.add(new CustomResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
}
return tokenGranters;
}
private AuthorizationServerTokenServices tokenServices() {
if (tokenServices != null) {
return tokenServices;
}
this.tokenServices = createDefaultTokenServices();
return tokenServices;
}
private AuthorizationServerTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(reuseRefreshToken);
tokenServices.setClientDetailsService(clientDetailsService);
addUserDetailsService(tokenServices, this.userDetailsService);
return tokenServices;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(userDetailsService));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
}
}
private OAuth2RequestFactory requestFactory() {
return new DefaultOAuth2RequestFactory(clientDetailsService);
}
private AuthorizationCodeServices authorizationCodeServices() {
if (this.authorizationCodeServices == null) {
this.authorizationCodeServices = new InMemoryAuthorizationCodeServices();
}
return this.authorizationCodeServices;
}
}
继承AbstractTokenGranter ,实现自定义的密码认证
模拟ResourceOwnerPasswordTokenGranter类,主要是针对密码验证过程中出现的异常,采取不同的策略
@Slf4j
public class CustomResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
private UserAccountDao userAccountDao;
//认证模式 在前端传递 grant_code = password
private static final String GRANT_TYPE = "password";
private final AuthenticationManager authenticationManager;
public CustomResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected CustomResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
//主要实现方法
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new linkedHashMap(tokenRequest.getRequestParameters());
String username = (String)parameters.get("username");
String password = (String)parameters.get("password");
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken)userAuth).setDetails(parameters);
userAccountDao = SpringContextUtils.getBean(UserAccountDao.class);
try {
//在这里会调用UserDetailServiceImpl里面的实现
userAuth = this.authenticationManager.authenticate(userAuth);
//账户密码正确 lock_flag 重置为 0
UpdateWrapper udpa = new UpdateWrapper<>();
udpa.setSql("lock_flag = 0 ");
udpa.eq("user_name",username);
UserAccount updateDto = new UserAccount();
userAccountDao.update(updateDto, udpa);
} catch (NonUsernameException var9) {
throw new UsernameNotFoundException("用户不存在");
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var10) {
try {
//账户密码错误 修改 lock_flag + 1
UpdateWrapper udpa = new UpdateWrapper<>();
udpa.setSql("lock_flag = lock_flag+1 ");
udpa.eq("user_name",username);
UserAccount updateDto = new UserAccount();
userAccountDao.update(updateDto, udpa);
}catch (Exception ex91){
throw new RuntimeException("错误次数累加失败");
}
throw new InvalidGrantException("账号密码错误,错误三次将锁定账户");
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}
实现UserDetailsService 获取用户信息的具体逻辑
这里主要是根据请求中的username获取数据库中加密后的密码,以便后面的逻辑进行密码匹配
具体逻辑根据实际场景实现
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserAccountDao userDao;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
//获取本地用户
QueryWrapper userQueryWrapper =new QueryWrapper<>();
userQueryWrapper.eq("user_name",userName).eq("delete_flag",0);
UserAccount user = userDao.selectOne(userQueryWrapper);
if(user != null){
//判断锁定次数是否超过三次
int lockValue = null == user.getLockFlag()?0:user.getLockFlag();
if(lockValue>=3){
throw new LockedException("密码尝试超过三次,账户已被锁定!");
}
UserDetails userr = User.builder()
.username(user.getUserName())
.password(user.getPassword())
.authorities(AuthorityUtils.createAuthorityList("ADMIN"))
.build();
return userr;
}else{
throw new NonUsernameException("用户不存在");
}
}
}
继承AuthorizationServerConfigurerAdapter 进行授权/认证服务器的配置
@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(RedisProperties.class)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private UserDetailServiceImpl userDetailService;
// 认证管理器
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private TokenGranter tokenGranter;
private RedisProperties redisProperties;
public AuthorizationServerConfig(RedisProperties redisProperties ){
this.redisProperties =redisProperties;
}
@Bean
public TokenStore tokenStore() {
//采用 Redis 存储
return new RedisTokenStore(redisConnectionFactory());
}
@Bean
public RedisConnectionFactory redisConnectionFactory(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(50);
poolConfig.setMaxWaitMillis(3000);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(false);
poolConfig.setTestWhileIdle(true);
JedisClientConfiguration clientConfig = JedisClientConfiguration.builder()
.usePooling().poolConfig(poolConfig).and().readTimeout(Duration.ofMillis(1000)).build();
// 单点redis
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(redisProperties.getIp());
redisConfig.setPort(redisProperties.getPort());
return new JedisConnectionFactory(redisConfig,clientConfig);
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Primary
@Bean
public DefaultTokenServices tokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
//开启支持refresh_token,此处如果之前没有配置,启动服务后再配置重启服务,可能会导致不返回token的问题,解决方式:清除redis对应token存储
tokenServices.setSupportRefreshToken(true);
//设置token有效期,默认12小时,此处修改为3小时
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 3);
//设置refresh_token的有效期,默认30天,此处修改为3天
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
return tokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//自定义授权模式
endpoints.tokenGranter(tokenGranter);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients() //如果使用表单认证则需要加上
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
}
继承WebSecurityConfigurerAdapter类,复写方法实现自定义安全访问策略
比较重要的方法
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//自定义用户认证逻辑 代码在后面贴出
@Override
@Bean
public UserDetailsService userDetailsService(){
return new UserDetailServiceImpl();
}
//认证管理
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //请求必须经过鉴权认证才能通过
.and().httpBasic()// 在请求头Authorization参数中附带认证编码
.and().cors()//跨域
.and().csrf().disable();//禁用跨站请求伪造
}
//权限过滤器,对于一些静态资源和不需要拦截的路由进行配置
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/error",
"/staticunauth/**"
);
}
}
Oauth2授权服务的相关配置就完成了。
关于服务端,我们也要配置Oauth2,拦截请求必须带有认证信息且认证有效才能访问接口@Configuration
@EnableResourceServer
@EnableConfigurationProperties(RedisProperties.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${security.oauth2.resource.id}")
public String resourceId;
private RedisProperties redisProperties;
public ResourceServerConfig(RedisProperties redisProperties ){
this.redisProperties =redisProperties;
}
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory());
}
@Bean
public RedisConnectionFactory redisConnectionFactory(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(50);
poolConfig.setMaxWaitMillis(3000);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(false);
poolConfig.setTestWhileIdle(true);
JedisClientConfiguration clientConfig = JedisClientConfiguration.builder()
.usePooling().poolConfig(poolConfig).and().readTimeout(Duration.ofMillis(1000)).build();
// 单点redis
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(redisProperties.getIp());
redisConfig.setPort(redisProperties.getPort());
return new JedisConnectionFactory(redisConfig,clientConfig);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
.tokenStore(tokenStore());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers(
"/v2/api-docs/**",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**"
).permitAll()
.anyRequest().authenticated()
.and()
//统一自定义异常
.exceptionHandling()
.and()
.csrf().disable();
}
}
最后
综上,我们完成了授权服务和业务服务的Oauth相关配置。
我们调用xxxx:xxx/oauth/token,传必要的参数,即可获取到返回的access_token信息
grant_type:password client_id:app client_secret:app username:ceshi password:123456
我们在访问业务服务的相关内容时,需要在请求头内添加以下内容,否则提示未授权
Authorization:Bearer edc5890e-8242-4333-859a-ab88cf2062eb



