springcloudsecurity+oauth+redis(jdbc)+mybatisplus实现授权认证+资源认证(基于授权码模式)
springcloudsecurity+oauth+redis+mybatisplus实现授权认证(基于授权码模式)
理解OAuth 2.0:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
1.技术栈;springboot+springcloudsecurity+mybatisplus
交互过程
oAuth 在 “客户端” 与 “服务提供商” 之间,设置了一个授权层(authorization layer)。“客户端” 不能直接登录 “服务提供商”,只能登录授权层,以此将用户与客户端区分开来。“客户端” 登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。“客户端” 登录授权层以后,“服务提供商” 根据令牌的权限范围和有效期,向 “客户端” 开放用户储存的资料
认证服务器
1.AuthorizationServerConfiguration.java
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/* @Bean
public TokenStore tokenStore() {
// 基于 JDBC 实现,令牌保存到数据库
return new JdbcTokenStore(dataSource);
}*/
//令牌保存到Redis*/
@Bean
public TokenStore tokenStore() {
RedisTokenStore redisToken = new RedisTokenStore(redisConnectionFactory);
return redisToken;
}
@Bean
public ClientDetailsService jdbcClientDetails() {
// 基于 JDBC 实现,需要事先在数据库配置客户端信息
return new JdbcClientDetailsService(dataSource);
}
/**
* 在此处定义认证管理,即系统或者集群中的用户以及 Token的存儲方式, 定义授权、token终端、token服务
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 自定义token生成方式,增加用户登录名等信息
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new MyTokenEnhancer()));
//通过注入authenticationManager 会自动开启password grants
// 设置令牌
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailsService)
.exceptionTranslator(e -> {
if (e instanceof OAuth2Exception) {
OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(new CustomOauthException(oAuth2Exception.getMessage()));
} else {
throw e;
}
});
}
/**
* 支持刷新token
*
* @return DefaultTokenServices
*/
@Primary
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAccessTokenValiditySeconds(60000);
defaultTokenServices.setRefreshTokenValiditySeconds(604800);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setReuseRefreshToken(false);
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 读取客户端配置
clients.withClientDetails(jdbcClientDetails());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer.allowFormAuthenticationForClients();
//允许 check_token 访问
oauthServer.checkTokenAccess("permitAll()");
}
2.WebSecurityConfiguration.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)//全局方法拦截
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authProvider() {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义认证与授权
auth
.authenticationProvider(authProvider())
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
// 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
web.ignoring().antMatchers("/oauth/check_token");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 通过HttpSecurity实现Security的自定义过滤配置
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
.and()
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and()
.formLogin().permitAll(); //新增login form支持用户登录及授权
}
}
3.UserDetailsServiceImpl.java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TbPermissionService tbPermissionService;
@Autowired
private TbUserService tbUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser tbUser = tbUserService.queryTbUserByUserName(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList();
if (StringUtils.isEmpty(tbUser)) {
throw new UsernameNotFoundException("很抱歉,找不到用户名为" + username + "的用户!");
}
if (tbUser != null) {
List<TbPermission> tbPermissions = tbPermissionService
.queryPermissionByUserId(tbUser.getId());
tbPermissions.forEach(tbPermission -> {
if (tbPermission != null && tbPermission.getEnname() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(tbPermission.getEnname());
grantedAuthorities.add(grantedAuthority);
}
});
}
/**
* 若只是认证,要去掉grantedAuthorities换成 Collections.emptyList()
* 若是要授权,要加上
*/
CustomUserPrincipal userDetail = new CustomUserPrincipal(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities);
userDetail.setTbUser(tbUser);
return userDetail;
}
资源服务器
4.ResourceServerConfiguration.java
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/*@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}*/
//
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//resourceId 推荐每个 受保护的资源都提供一下 ,可以供 auth服务对资源进行一个认证
resources.resourceId("ls_resource").tokenStore(tokenStore());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
.antMatchers("/contents").hasAuthority("SystemContent")
.antMatchers("/view/**").hasAuthority("SystemContentView")
.antMatchers("/insert/**").hasAuthority("SystemContentInsert")
.antMatchers("/update/**").hasAuthority("SystemContentUpdate")
.antMatchers("/delete/**").hasAuthority("SystemContentDelete");
}
}
5.localhost:8080/oauth/authorize?client_id=client&response_type=code获取code
用户名:admin 密码:123456
6.获取token
7.访问资源
8.其他代码请见GitHub:https://github.com/smileLs66/springcloud/tree/master/auth-oauth2
参考:
https://www.funtl.com/zh/guide/Spring-Security-oAuth2.html
https://www.cnblogs.com/cjsblog/p/10548022.html
阮一峰的jwt入门教程:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
阮一峰理解OAuth2.0: https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html