Springboot Security +Oauth2 微服务单点登录授权认证
1.介绍
客户端向资源服务器发起请求后,资源服务器会首先拿接受的token去权限服务器验证,如果验证通过,才继续执行请求。
1.1 提供的四个授权模式:
1.授权码模式(authorization_code):
2.客户端认证
3.密码认证
4.隐式授权认证
5.刷新密钥
我们会在认证请求的grant_type参数中指定采用何种方式去进行认证。比如下面的一个链接,通过grant_type=authorization_code指定采用授权码认证。
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client&client_secret=123456&code=jRvetc&redirect_uri=http://www.baidu.com
下面是一些默认的端点 URL:
/oauth/authorize:授权端点
/oauth/token:令牌端点
/oauth/confirm_access:用户确认授权提交端点
/oauth/error:授权服务错误信息端点
/oauth/check_token:用于资源服务访问的令牌解析端点
/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话
授权端点的 URL 应该被 Spring Security 保护起来只供授权用户访问
2.代码配置
2.1配置AuthorizationServerConfigurerAdapter.java
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private OauthClientDetailServiceImpl oauthClientDetailService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecurityConfig securityConfig;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
RedisConnectionFactory redisConnectionFactory;
private static final String Resource_Ids = "order";
@Resource
private DataSource dataSource;
/**
* 客户端信息配置
* clientId:用来表示客户端ID,必须
* secret:客户端安全码,
* scope:用来限制客户端的访问范围,如果是空的画,那么客户端拥有全部的访问范围
* authrizedGrantTypes:此客户端可以使用的授权类型,默认为空
* authorities:此客户端可以使用的权限
* redirectUris:回调地址,授权服务回往该回调地址推送此客户端的相关的信息
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置两个客户端,一个用于password认证一个用于client认证(数据库版)
// clients.jdbc(dataSource)
//配置两个客户端,一个用于password认证一个用于client认证(内存版)
clients.inMemory()
//client模式
.withClient("client_1")//clientId
.secret(passwordEncoder.encode("client_1"))//客户端密钥
.resourceIds(Resource_Ids)//客户端拥有的资源列表
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("select")//允许的授权范围
.authorities("oauth2")//此客户端可以使用的权限
.and()
//密码模式
.withClient("client_2")
.secret(passwordEncoder.encode("client_2"))
.resourceIds("smeltingSteel")
.authorizedGrantTypes("password", "refresh_token")
.scopes("select")
.authorities("oauth2");
}
@Autowired
public UserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.userDetailsService(userDetailsService)
.tokenStore(new RedisTokenStore(redisConnectionFactory))
//启用oauth2管理
.authenticationManager(authenticationManager)
//接收GET和POST
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
;
}
//配置认证相关属性
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.allowFormAuthenticationForClients() // 允许 /oauth/token的端点表单认证
.checkTokenAccess("permitAll()") //公开 /oauth/checkout_token接口
.tokenKeyAccess("permitAll()");//公开 /oauth/token_key接口
}
}
2.2配置WebSecurityConfig
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Autowired
private UserDetailsService userDetailsService;
/**
* 如果调用super.configure 默认使用父类型的默认配置
* 默认规则是用户名必须是username 密码必须是password
* 此逻辑由UsernamePasswordAuthenticationFilter规定
*
* @param zfyy
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 认证部分
http.formLogin()
.usernameParameter("username").passwordParameter("password");//配置前端请求的用户名和密码参数名称
http.authorizeRequests()
.antMatchers("/favicon.ico","/oauth/**").permitAll()
.antMatchers("/oauth/token").permitAll()
http.csrf().disable();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource) {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 设置启动的时候创建表格,只有数据库中不存在表d格的时候可以使用,默认值是false,第二次启动需要注释掉
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2.3配置ResourceServerConfig.java
@Configuration
@EnableResourceServer
public class myResourceServerConfigurer extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("smeltingSteel")//资源Id
.tokenServices(tokenServices())//使用远程服务验证令牌的服务
.stateless(true);//无状态模式
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//校验请求
.antMatchers("/springbootSecurity/**")//路径匹配规则
.access("#oauth2.hasScope('select')")//需要匹配scope
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
// 配置access_token远程验证策略
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl("http://localhost:9001/oauth/check_token");
services.setClientId("client_2");
services.setClientSecret("client_2");
return services;
}
}
2.4配置数据库用户 userDetailsServiceImpl.java
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfoPO userInfoPO = userInfoService.selectByUserName(username);
if (userInfoPO == null) {
throw new UsernameNotFoundException("用户未查询到");
}
// 用户登录成功后 查询用户的权限集合,包括角色和权限
// 在springSecurity 对角色的命名有严格规则,要求角色名称的前缀必须是ROLE_ 不要在数据库中保存
// 在springSecurity中,角色和权限平等的,都代表用户的访问权限。
List<String> roles = userInfoService.findRolesByUser(userInfoPO);
List<String> resources = userInfoService.findResourcesByUser(userInfoPO);
// 权限集合
String[] authorities = new String[roles.size() + resources.size()];
for (int i = 0; i < roles.size(); i++) {
authorities[i] = "ROLE_" + roles.get(i);
}
for (int i = 0; i < resources.size(); i++) {
authorities[i + roles.size()] = resources.get(i);
}
return new User(userInfoPO.getName(), userInfoPO.getPassword(), AuthorityUtils.createAuthorityList(authorities));
}
}