使用oauth2保护你的应用,可以分为简易的分为三个步骤
- 配置资源服务器
- 配置认证服务器
- 配置spring security
前两点是oauth2的主体内容,但前面我已经描述过了,spring security oauth2是建立在spring security基础之上的,所以有一些体系是公用的。
oauth2根据使用场景不同,分成了4种模式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
本文重点讲解接口对接中常使用的密码模式(以下简称password模式)和客户端模式(以下简称client模式)。授权码模式使用到了回调地址,是最为复杂的方式,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式。简化模式不常用。
配置资源服务器和授权服务器
由于是两个oauth2的核心配置,我们放到一个配置类中。
为了方便下载代码直接运行,我这里将客户端信息放到了内存中,生产中可以配置到数据库中。token的存储一般选择使用redis,一是性能比较好,二是自动过期的机制,符合token的特性。
1 @Configuration
2 public class OAuth2ServerConfig {
3
4 private static final String RESOURCE_ID = "wymlib";
5 //资源配置服务器
6 @Configuration
7 @EnableResourceServer
8 @Order(110)
9 protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
10
11 @Override
12 public void configure(ResourceServerSecurityConfigurer resources) {
13 resources.resourceId(RESOURCE_ID).stateless(true);
14 }
15
16 @Override
17 public void configure(HttpSecurity http) throws Exception {
18 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
19 .and()
20 .requestMatchers().anyRequest()
21 .and()
22 .anonymous()
23 .and()
24 .authorizeRequests()
25 .antMatchers("/api/v1/**").authenticated();//配置访问控制,必须认证过后才可以访问
26 }
27 }
28
29 //认证服务器
30 @Configuration
31 @EnableAuthorizationServer
32 @Order(99)
33 protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
34
35 @Autowired
36 AuthenticationManager authenticationManager;
37 // @Autowired
38 // RedisConnectionFactory redisConnectionFactory;
39
40 @Value("${config.oauth2.clientID}")
41 String clientID;
42
43 @Value("${config.oauth2.clientSecret}")
44 String clientSecret;
45
46 @Value("${config.oauth2.accessTokenValiditySeconds}")
47 int accessTokenValiditySeconds;
48
49 @Override
50 public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
51 clients.inMemory().withClient(clientID)
52 .resourceIds(RESOURCE_ID)
53 .authorizedGrantTypes("client_credentials", "refresh_token")
54 .scopes("select")
55 .authorities("client")
56 .secret(clientSecret)
57 .accessTokenValiditySeconds(accessTokenValiditySeconds);
58 }
59
60 /*@Override
61 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
62 endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
63 .authenticationManager(authenticationManager);
64 }*/
65
66 @Override
67 public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
68 //允许表单认证
69 oauthServer.allowFormAuthenticationForClients();
70 }
71
72 }
73
74 }
简单说下spring security oauth2的认证思路。
-
client模式,没有用户的概念,直接与认证服务器交互,用配置中的客户端信息去申请accessToken,客户端有自己的client_id,client_secret对应于用户的username,password,而客户端也拥有自己的authorities,当采取client模式认证时,对应的权限也就是客户端自己的authorities。
-
password模式,自己本身有一套用户体系,在认证时需要带上自己的用户名和密码,以及客户端的client_id,client_secret。此时,accessToken所包含的权限是用户本身的权限,而不是客户端的权限。
我对于两种模式的理解便是,如果你的系统已经有了一套用户体系,每个用户也有了一定的权限,可以采用password模式;如果仅仅是接口的对接,不考虑用户,则可以使用client模式。
配置spring security
在spring security的版本迭代中,产生了多种配置方式,建造者模式,适配器模式等等设计模式的使用,spring security内部的认证flow也是错综复杂,在我一开始学习ss也产生了不少困惑,总结了一下配置经验:使用了springboot之后,spring security其实是有不少自动配置的,我们可以仅仅修改自己需要的那一部分,并且遵循一个原则,直接覆盖最需要的那一部分。这一说法比较抽象,举个例子。比如配置内存中的用户认证器。有两种配置方式
1 planA:
2
3 @Bean
4 protected UserDetailsService userDetailsService(){
5 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
6 manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());
7 manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());
8 return manager;
9 }
10 planB:
11
12 @Configuration
13 @EnableWebSecurity
14 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
15
16 @Override
17 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
18 auth.inMemoryAuthentication()
19 .withUser("user_1").password("123456").authorities("USER")
20 .and()
21 .withUser("user_2").password("123456").authorities("USER");
22 }
23
24 @Bean
25 @Override
26 public AuthenticationManager authenticationManagerBean() throws Exception {
27 AuthenticationManager manager = super.authenticationManagerBean();
28 return manager;
29 }
30 }
你最终都能得到配置在内存中的两个用户,前者是直接替换掉了容器中的UserDetailsService,这么做比较直观;后者是替换了AuthenticationManager,当然你还会在SecurityConfiguration 复写其他配置,这么配置最终会由一个委托者去认证。如果你熟悉spring security,会知道AuthenticationManager和AuthenticationProvider以及UserDetailsService的关系,他们都是顶级的接口,实现类之间错综复杂的聚合关系…配置方式千差万别,但理解清楚认证流程,知道各个实现类对应的职责才是掌握spring security的关键。
下面给出我最终的配置:
1 @Configuration
2 @EnableWebSecurity
3 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
4
5 @Bean
6 @Override
7 protected UserDetailsService userDetailsService(){
8 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
9 manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());
10 manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());
11 return manager;
12 }
13
14 @Override
15 protected void configure(HttpSecurity http) throws Exception {
16 // @formatter:off
17 http
18 .requestMatchers().anyRequest()
19 .and()
20 .authorizeRequests()
21 .antMatchers("/oauth/*").permitAll();
22 // @formatter:on
23 }
24 }
重点就是配置了一个UserDetailsService,和ClientDetailsService一样,为了方便运行,使用内存中的用户,实际项目中,一般使用的是数据库保存用户,具体的实现类可以使用JdbcDaoImpl或者JdbcUserDetailsManager。
xml配置:
1 <!-- OAuth2 URL: /oauth/token 的处理与配置 一般使用时这里不需要修改, 直接使用即可 -->
2 <sec:http pattern="/oauth/token" create-session="stateless"
3 authentication-manager-ref="oauth2AuthenticationManager"
4 entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false">
5 <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
6 <sec:anonymous enabled="false" />
7 <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint" />
8 <sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
9 before="BASIC_AUTH_FILTER" />
10 <sec:access-denied-handler ref="oauth2AccessDeniedHandler" />
11 <!-- <csrf disabled="true"/> -->
12 </sec:http>
13
14
15
16 <sec:authentication-manager id="oauth2AuthenticationManager">
17 <sec:authentication-provider
18 user-service-ref="oauth2ClientDetailsUserService" />
19 </sec:authentication-manager>
20
21
22 <beans:bean id="oauth2ClientDetailsUserService"
23 class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
24 <beans:constructor-arg ref="clientDetailsService" />
25 </beans:bean>
26
27 <!-- 管理 ClientDetails -->
28 <beans:bean id="clientDetailsService"
29 class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
30 <beans:constructor-arg index="0" ref="dataSource" />
31 </beans:bean>
32
33 <beans:bean id="oauth2AuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
34
35 <!-- 处理grant_type=client_credentials 的逻辑 只从请求中获取client_id与client_secret -->
36 <beans:bean id="clientCredentialsTokenEndpointFilter"
37 class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
38 <beans:property name="authenticationManager" ref="oauth2AuthenticationManager" />
39 </beans:bean>
40
41
42
43
44 拦截api/v1:
45
46 <sec:http pattern="/api/v1/**" create-session="never"
47 entry-point-ref="oauth2AuthenticationEntryPoint"
48 access-decision-manager-ref="oauth2AccessDecisionManager"
49 use-expressions="false">
50 <sec:anonymous enabled="false" />
51 <sec:intercept-url pattern="/api/v1/**"
52 access="IS_AUTHENTICATED_ANONYMOUSLY" />
53 <sec:custom-filter ref="unityResourceServer" before="PRE_AUTH_FILTER" />
54 <sec:access-denied-handler ref="oauth2AccessDeniedHandler" />
55 </sec:http>
56
57 <!-- 扩展Spring Security 默认的 AccessDecisionManager 添加对OAuth中 scope 的检查与校验 -->
58 <beans:bean id="oauth2AccessDecisionManager"
59 class="org.springframework.security.access.vote.UnanimousBased">
60 <beans:constructor-arg>
61 <beans:list>
62 <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
63 <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
64 <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
65 </beans:list>
66 </beans:constructor-arg>
67 </beans:bean>
68
69 <!-- 每一个资源(resource)的定义, resource-id必须唯一, OauthClientDetails中的resourceIds属性的值由此来的,
70 允许一个Client有多个resource-id, 由逗号(,)分隔 每一个定义会在Security Flow中添加一个位于 PRE_AUTH_FILTER
71 之前的Filter -->
72 <!--unity resource server filter -->
73 <oauth2:resource-server id="unityResourceServer"
74 resource-id="unity-resource" token-services-ref="tokenServices" />
75
76 <beans:bean id="tokenServices"
77 class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
78 <beans:property name="tokenStore" ref="tokenStore" />
79 <beans:property name="clientDetailsService" ref="clientDetailsService" />
80 <beans:property name="supportRefreshToken" value="true" />
81 <beans:property name="accessTokenValiditySeconds" value="30" />
82 </beans:bean>
83
84 <!--Config token services -->
85 <!--<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore"/> -->
86 <beans:bean id="tokenStore"
87 class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
88 <beans:constructor-arg index="0" ref="dataSource" />
89 </beans:bean>
90
91
92
93 <!-- Security OAuth Flow的核心配置 每一个配置对应一类具体的grant_type 可根据需求删除或禁用, 如: <oauth2:implicit
94 disabled="true"/> 默认支持OAuth2提供的5类grant_type, 若不需要任何一类, 将其配置注释掉(或删掉)即可. 若需要自定义
95 authorization url, 在 <oauth2:authorization-server > 配置中添加authorization-endpoint-url,如:
96 authorization-endpoint-url="/oauth2/authorization" 若需要自定义 token url, 在 <oauth2:authorization-server
97 > 配置中添加token-endpoint-url配置, 如:token-endpoint-url="/oauth2/my_token" -->
98 <oauth2:authorization-server
99 client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
100 user-approval-handler-ref="oauthUserApprovalHandler"
101 user-approval-page="oauth_approval" error-page="oauth_error">
102 <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices" />
103 <oauth2:implicit />
104 <oauth2:refresh-token />
105 <oauth2:client-credentials />
106 <oauth2:password />
107 </oauth2:authorization-server>
108
109
110 <beans:bean id="oauthUserApprovalHandler"
111 class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
112 <beans:property name="tokenStore" ref="tokenStore" />
113 <beans:property name="clientDetailsService" ref="clientDetailsService" />
114 <beans:property name="requestFactory" ref="oAuth2RequestFactory" />
115 <!-- <beans:property name="oauthService" ref="oauthService"/> -->
116 </beans:bean>
117
118 <!-- 管理 Authorization code -->
119 <beans:bean id="jdbcAuthorizationCodeServices"
120 class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices">
121 <beans:constructor-arg index="0" ref="dataSource" />
122 </beans:bean>
123
124
125
126 请求token的url:
127 http://192.9.8.144/ymlib/oauth/token?client_id=aa086e67c36342ed9ab6519247f5b68b&client_secret=mQDUMa03Rdy5vcMWjYHJmitkWJi3Rosr&grant_type=client_credentials
参考资料:
从零开始的Spring Security Oauth2(一)
从零开始的Spring Security Oauth2(二)
从零开始的Spring Security Oauth2(三)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架