文章部分图片来自参考资料,这篇文章主要讲 spring security oauth
概述
上一篇我们学习了 SS 中重要的工作原理和几个大概的认证和授权过程。而 spring security oauth 用到的就是 spring security 知识,我们学习 sso 之前先看一下oauth 是什么,可以学习阮一峰老师的文章
oauth 的流程图如下 : (牢牢记住这张图)
主要的角色有资源持有者,资源服务器,认证服务器,还有用户
授权(获取 Access Token)的方式有多种方式
- 授权码
- 简化模式
- 客户端模式
- 密码模式
oauth 可以理解成工作中,你(Client)去出差,回来需要报销,会计(Authorzation Server)首先需要你请示老板(Resource Owned)是否同意给你报销出差费用,假如同意了,你就回来找会计,把老板的凭证给她,她会给你一个token (获取token过程的方式有多种,就是前面提到的), 然后你带着 token 再去财务(Resource Server)领钱 ,结束流程。
Spring Security Oauth
学习 Spring Security Oauth ,先学习一个例子(出处),然后根据例子配合oauth 流程学习
我们按照上面的例子敲完代码后,整个流程走完再结合oauth 授权的流程
例子中使用的授权码,而获取Access Token ,为何先给授权码,而不直接给 Access Token 呢 ?
给授权码,再用授权码去获取Access Token 的原因是授权码可以让服务端知道client 的身份。
spring security oauth 角色
oauth 获取中几个重要的角色中在 spring security oauth 中对应的有 :
- @EnableResourceServer
- @EnableAuthorazaitonServer
- @EnableOauthClient
Resource Owned 的角色放在 Authorazation Server,就是代码中的 UserDetail
UserDetail
UserDetail 的作用是用来认证即是上面oauth 流程图的A 步骤,示例如下 :
@Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UserService userService; /** * 授权的时候是对角色授权,而认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = userService.getUserByName(username); if (null == sysUser) { throw new UsernameNotFoundException(username); } List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (SysRole role : sysUser.getRoleList()) { for (SysPermission permission : role.getPermissionList()) { authorities.add(new SimpleGrantedAuthority(permission.getCode())); } } return new User(sysUser.getUsername(), sysUser.getPassword(), authorities); } } @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } }
TokenStore
获取token,那么很明显需要一个储存token 的容器,例如我们想使用 Redis 来存储token ,当我们需要实现自己token 存储容器时可以如下使用 :
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ... /** * 该方法是用来配置Authorization Server endpoints的一些非安全特性的,比如token存储、token自定义、授权类型等等的 * 默认情况下,你不需要做任何事情,除非你需要密码授权,那么在这种情况下你需要提供一个AuthenticationManager */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(new MyRedisTokenStore(redisConnectionFactory)); } } @Service public class MyRedisTokenStore implements TokenStore { .... }
clientDetail
使用授权码方式获取 Access Token 时先发放授权码,而是否可以发送授权码需要验证client 的身份,clientDetail 便是便是表述 client 基本信息的类
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ... @Resource private DataSource dataSource; /** * 配置ClientDetailsService * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一个AuthenticationManager,否则密码授权方式不可用。 * 至少配置一个client,否则服务器将不会启动。 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } }
token 和 clientDetail 基类表述
可以参考此处构建自己的 token 和 clientDetail ,以下是数据库实现
-- used in tests that use HSQL create table oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256) ); create table oauth_client_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256) ); create table oauth_access_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication LONGVARBINARY, refresh_token VARCHAR(256) ); create table oauth_refresh_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication LONGVARBINARY ); create table oauth_code ( code VARCHAR(256), authentication LONGVARBINARY ); create table oauth_approvals ( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP ); -- customized oauth_client_details table create table ClientDetails ( appId VARCHAR(256) PRIMARY KEY, resourceIds VARCHAR(256), appSecret VARCHAR(256), scope VARCHAR(256), grantTypes VARCHAR(256), redirectUrl VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additionalInformation VARCHAR(4096), autoApproveScopes VARCHAR(256) );
完整服务端配置
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**","/login/**", "/logout").permitAll() .anyRequest().authenticated() // 其他地址的访问均需验证权限 .and() .formLogin() .loginPage("/login") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/assets/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private DataSource dataSource; @Autowired RedisConnectionFactory redisConnectionFactory; @Autowired private AuthenticationManager authenticationManager; /** * 配置授权服务器的安全,意味着实际上是/oauth/token端点。 * /oauth/authorize端点也应该是安全的 * 默认的设置覆盖到了绝大多数需求,所以一般情况下你不需要做任何事情。 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); } /** * 配置ClientDetailsService * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一个AuthenticationManager,否则密码授权方式不可用。 * 至少配置一个client,否则服务器将不会启动。 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } /** * 该方法是用来配置Authorization Server endpoints的一些非安全特性的,比如token存储、token自定义、授权类型等等的 * 默认情况下,你不需要做任何事情,除非你需要密码授权,那么在这种情况下你需要提供一个AuthenticationManager */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(new MyRedisTokenStore(redisConnectionFactory)); } }
当资源服务器和认证服务器是同一个服务器的时候 :
Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**","/login/**", "/logout").permitAll() .anyRequest().authenticated() // 其他地址的访问均需验证权限 .and() .formLogin() .loginPage("/login") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/assets/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
工作流程
http://blog.didispace.com/xjf-spring-security-1/
http://blog.didispace.com/xjf-spring-security-1/
总结
文章并不是 spring oauth 的入门篇,主要是结合 oauth 的流程图找到对应 spring security oauth 框架中的逻辑对应,更好地自定义改造。结合下面的参考资料,可以完成单点登录的功能。
参考资料
- https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/#ns-custom-filters(自定义filter,用到的时候推荐一看)
- http://www.spring4all.com/article/422
- 下面是 spring 文档
- https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/
- https://spring.io/guides
- https://spring.io/guides/tutorials/spring-boot-oauth2/
- 下面是 spring oauth client 的资料
- https://stackoverflow.com/questions/27864295/how-to-use-oauth2resttemplate
- https://github.com/mariubog/oauth-client-sample
- JWT 的介绍
- https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for=pc
- 可能遇到的问题
- https://blog.csdn.net/xiaoxiaomao123098/article/details/86179989
原文地址:https://www.cnblogs.com/Benjious/p/10638913.html