Raf*_*Raf 1 java spring spring-security oauth-2.0 spring-security-oauth2
我开始使用Spring OAuth2,在这个过程中,我很难找到相关的教程和内容,主要是因为以下内容
我设法编写了我的实现,解决了上述问题,现在我想分享我的发现,以便拯救他人的痛苦.
请参阅我的答案,了解我所遵循的方法,如果您有任何建议,请随时分享您的建议,建议和反馈.
这个问题的主要目标是
Raf*_*Raf 10
I started off with basic setup of Spring Security framework using mainly XML configuration.
Spring Security OAuth2 I did a ton of google searches and looked at a bunch of repositories including the main Spring OAuth 2 repo.
I started off with OAuth2 XML Configs and I needed to make changes in order to
/oauth/check_token
并验证JWT令牌自定义声明而且,我已经达到了以下目的
上面链接的默认spring-security.xml包含以下假设
I would like to keep the Authentication and Resource server separate hence, I stripped off all the protected endpoints configs and the in-memory user-service and oauth-client-details.
To achieve this, I made sure that
org.springframework.security.core.userdetails.UserDetails
and overrides the getAuthorities which return a collection of GrantedAuthorityorg.springframework.security.core.GrantedAuthority
and override the method getAuthority I now need to inform the Spring OAuth2 about the above customised MyUser and MyRole and in order to do that, I needed to do the following
org.springframework.security.core.userdetails.UserDetailsService
interface and override the inherited loadByUsername method to query my custom user database table and retrieve the user with its roles and construct an instance of org.springframework.security.core.userdetails.User
and return it. Create a <bean>
of the above custom implementation and let's call it MyUserDetailsService
and place it in spring-security.xml config file. Something as follow
<bean id="myUserDetailsService" class="your.package.hierarcy.goes.here.MyUserDetailsService" />
Just defining a bean is once again not enough, we have to tell Spring OAuth2 to use the MyUserDetailsService
and to do that we need to inject the myUserDetailsService
into Spring OAuth2's default authentication provider or our own authentication provider that will then get passed to authentication-manager element, as shown below
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="myUserDetailsService"/>
<!-- if you encode or encrypt your password, then pass encoder-->
<!--<property name="passwordEncoder" ref="passwordEncoder"/>-->
</bean>
Run Code Online (Sandbox Code Playgroud)
最后,我们需要将上述身份验证提供程序注入spring-security.xml中指定的Authentication Manager ,如下所示
<!-- Original -->
<authentication-manager alias="authenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider>
<user-service id="userDetailsService">
<user name="marissa" password="koala" authorities="ROLE_USER" />
<user name="paul" password="emu" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
Run Code Online (Sandbox Code Playgroud)
用以下内容进行更改
<!-- Modified with our own implementation of UserDetailsService -->
<authentication-manager alias="authenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider ref="daoAuthenticationProvider" />
</authentication-manager>
Run Code Online (Sandbox Code Playgroud)
而现在使用它使用上面的认证管理器,它在传递给补助流程<oauth:authorization-server>
如下
<oauth:password authentication-manager-ref="authenticationManager"/>
Run Code Online (Sandbox Code Playgroud)
如果仔细查看上面链接的官方Spring OAuth2 repo 中的默认spring-security.xml,您将看到以下引用链
clientCredentialsTokenEndpointFilter
豆
引用
clientAuthenticationManager
豆
引用
clientDetailsUserDetailsService
引用
clientDetails
并且clientDetails
只是在<oauth:client-details-service id="clientDetails">
标签末尾声明的固定oauth客户端列表.
Alright, so the objective is to instruct Spring OAuth2 to read and store oauth_clients to/from a database. If we do not need to customize the the default spring oauth database tables to meet our specific needs, then its quite easy and the process is as follow
clientDetailsUserDetailsService
to pass the default Spring OAuth2 org.springframework.security.oauth2.provider.client.JdbcClientDetailsService
and declare the same as a bean, as shown below. Defautlt Spring OAuth2 JdbcClientDetailsService bean definition
<bean class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService" id="myClientDetails">
<constructor-arg index="0">
<!-- This is your jdbc datasource, i.e. db details-->
<ref bean="dataSource" />
</constructor-arg>
</bean>
Run Code Online (Sandbox Code Playgroud)
and now the clientDetailsUserDetailsService bean should look as follow
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="myClientDetails" />
</bean>
Run Code Online (Sandbox Code Playgroud)
And everything else the stays the same. The above changes, will instruct the Spring OAuth2 to read oath_clients from the database (oauth_client_details) rather than hard-coded xml config.
What if, you wanted to modify the default oauth_client_details table to adda few custom columns to meet your specific needs, will in that case you need to make a different set of changes. So we have the following objective
given the following (in the default spring-security.xml)
clientCredentialsTokenEndpointFilter
bean
references
clientAuthenticationManager
bean
references
clientDetailsUserDetailsService
references
clientDetails
We need to transform the above workflow (or chain of references) in order for Spring OAuth2 to be able to access our new customized oauth_client_details database table. So the new workflow or chain of references should be similar to below
clientCredentialsTokenEndpointFilter
bean (no change)
references
clientAuthenticationManager
bean (no change)
references
clientDetailsUserDetailsService
(to be updated)
references
clientDetails
(to be replaced)
And to achieve the above, lets move from bottom to top. The default Spring OAuth2 uses org.springframework.security.oauth2.provider.ClientDetails
to load oauth_client from the default oauth_client_details table. We need to provide a custom implementation for org.springframework.security.oauth2.provider.ClientDetails
by implementing it hence, something as follow
public class MyClientDetails implements ClientDetails { ... }
Run Code Online (Sandbox Code Playgroud)
Before we move on, I just wanted to mention that Spring OAuth2 already provides an implementation for the above interface meaning that we could make our life a lot easier by actually extending the only implementation of the above interface (ClientDetails) which is org.springframework.security.oauth2.provider.client.BaseClientDetails
rather than implementing it from the beginning (leverage all that can be done from BaseClientDetails and add our own custom fields) hence, the MyClientDetails looks as follow then
public class MyClientDetails extends BaseClientDetails {
//fields representing custom column for oauth_client
//getters and setters
//make sure to call super() in the inherited constructors, before
//setting custom fields.
}
Run Code Online (Sandbox Code Playgroud)
Alright, now that we have our own ClientDetails object, just like #2 we need to implement our own JdbcClientDetailsService that will get injected to clientDetailsUserDetailsService
. In order to implement our own JdbcClientDetailsService let's look at the signature of the default method
public class JdbcClientDetailsService
extends Object
implements ClientDetailsService, ClientRegistrationService
Run Code Online (Sandbox Code Playgroud)
As you can see, the above class implements ClientDetailsService & ClientRegistrationService
interfaces. While implementing our own JdbcClientDetailsService I do not recommend extending the default JdbcClientDetailsService because there are quite a few private class variables (and methods) which will not be inherited hence, let's use the default implementation to write our own.
public class MyJdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService {
//override loadClientByClientId method, query the custom
//oauth_client_details database table and populate the
//MyClientDetails object created above and return it.
//Implement other methods to CRUD oauth_clients
}
Run Code Online (Sandbox Code Playgroud)
Now that you implemented MyJdbcClientDetailsService create a bean for it in spring-security.xml, in the following lines
Now inject the above bean to clientDetailsUserDetailsService
, and looking into default spring-security.xml, we have the following
<!-- Original referencing hardcoded clients -->
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="clientDetails" />
</bean>
Run Code Online (Sandbox Code Playgroud)
And the above will need to be changed to as follow
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="myJdbcClientDetailsService" />
</bean>
Run Code Online (Sandbox Code Playgroud)
There we go, now the chain looks as follow after specifying our own ClientDetailsService:
clientCredentialsTokenEndpointFilter
bean (no change)
references
clientAuthenticationManager
bean (no change)
references
clientDetailsUserDetailsService
(ref updated)
references
myJdbcClientDetailsService
(our customized client details service)
The above instructs the Spring OAuth2 to look up a customized database table for oauth_client details.
JWT Token or JSON Web Token can be used among three different actors of OAuth2 (Authorization server actor, Resource Server actor, Client actor) in order to communicate with each other in performing different actions relating to authorization and access of protected resources. The JWT token is used by Authorization server and it is signed using a public/private key and the objective is to make sure that the content of the JWT token do not get updated (unless someone have access to the private key). The basic workflow can be as follow (Let's assume password grant flow)
Above briefly describes how the JWT token is used by OAuth2. There is a ton of more details on JWT and varieties of JWT out there (signed, signed and encrypted, etc etc).
For more information on JWT implementation of Spring OAuth2, see the official repo here and JWT webpage.
Let's see, we have the following JWT
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Run Code Online (Sandbox Code Playgroud)
and we would like to include a set of scopes to the above token, so the Resource server actor know whether this user is allowed to make this request or not. The change could look as follow
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"scope": "read write delete"
}
Run Code Online (Sandbox Code Playgroud)
To add custom claims (each of above key/value is referred to as claims), we need to create a class MyTokenEnhancer which implement org.springframework.security.oauth2.provider.token.TokenEnhacer
interface and override it's enhance method OR extend org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
which in turn implements the default TokenEnhancer and override it's enhance method.
By overriding the enhance method, you can add custom claims to the OAuth2AccessToken. Please see the above linked repo for more details on this.
For me, the oauth clients send a customized OAuth2Request and I needed more than the deafult DefaultOAuth2RequestValidator implementation hence, I had to write my own and implemented the org.springframework.security.oauth2.provider.OAuth2RequestValidator
. Something in the following lines
public class MyOAuth2RequestValidator implements OAuth2RequestValidator {
//override the necessary methods
}
Run Code Online (Sandbox Code Playgroud)
Now that we have created our own custom MyOAuth2RequestValidator how should we instruct the Token endpoint to use it using XML configuration? Well, this one took me a little time for figure it out. Looking into the default spring-security.xml we have the following
<oauth:authorization-server
client-details-service-ref="clientDetails" token-services-ref="tokenServices"
user-approval-handler-ref="userApprovalHandler">
<oauth:authorization-code />
<oauth:implicit />
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password />
</oauth:authorization-server>
Run Code Online (Sandbox Code Playgroud)
When we make an OAuth2 request to the /oauth/token
endpoint, the request is mapped to ClientCredentialsTokenEndpointFilter
first, and from there at some stage the request is delegated to org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
and if you look at the source code TokenEndpoint you will see the following
private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
Run Code Online (Sandbox Code Playgroud)
Now to replace that with our Custom validator MyOAuth2RequestValidator
we need to do the following
<oauth:authorization-server>
in the spring-security to use our custom validator from #1 See below the updated authorization-server tag
<oauth:authorization-server
client-details-service-ref="clientDetails" token-services-ref="tokenServices"
user-approval-handler-ref="userApprovalHandler"
request-validator-ref="myOAuth2RequestValidator">
<oauth:authorization-code />
<oauth:implicit />
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password />
</oauth:authorization-server>
Run Code Online (Sandbox Code Playgroud)
And that's it. Now, every time the TokenEndpoint is hit, it will use our custom validator instead of default one.
We might not use all of the available grant flows hence, make sense to disable those not used. Let's say, I don't want to use refresh-token grant flow (just an example) and to disable it we have to update the <oauth:authorization-server>
The authorization server with disabled refresh-token grant flow looks as follow
You can also disable other flows. Obviously you can get away with not disabling it because, if you do not support let's say Authorization Code grant flow then simply do not register an oauth_client that has a value of authorization_code in it's oauth_client_table (if default oauth table)'s authorized_grant_types column.
Also to share with you an experience that helped me understand Spring OAuth2, I actually debugged the Spring OAuth2 classes to understand how each workflow worked and kept track of all the classes that got hit during different grant flows. It's helpful to give you an overall understanding of how Spring OAuth2 works and then it will become less painful to implement your own OAuth2 implementation on top of Spring OAuth2.
If the Resource and Authorization server are not in the same server and not sharing the same database, then Resource server requires to double check with Authorization server after it receives a request for a resource to make sure that the token is still valid (this is useful when tokens don't expire too soon) and has the right permissions/claims. In order to achieve this Spring OAuth provides Check Token Endpoint that can be enabled by adding the following
check-token-enabled="true"
Run Code Online (Sandbox Code Playgroud)
to the <oauth:authorization-server ...>
element in the XML configuration file. Once the above is added, a POST request to {server-url}/oauth/check_token
with a form parameter with key token
and value JWT access token
will instruct the Authorization server to validate that the token is valid. The default implementation does the default checks such as
And you will have to do a little bit of customization to validate your custom claims. See my other post in here for more details.
归档时间: |
|
查看次数: |
2657 次 |
最近记录: |