How do you configure Spring 4.0 and Spring Security (3.2.0) for digest authentication exclusively using javaconfig (no XML)? I am using the below configuration class, however all requests are getting denied with HTTP 401 and "Nonce should have yielded two tokens but was (... message just stops there)".
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurationDigest extends WebSecurityConfigurerAdapter
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().antMatchers("/**").authenticated().and().addFilter(digestAuthenticationFilter(digestEntryPoint()));
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception
{
return super.userDetailsServiceBean();
}
public DigestAuthenticationFilter digestAuthenticationFilter(DigestAuthenticationEntryPoint digestAuthenticationEntryPoint) throws Exception
{
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setAuthenticationEntryPoint(digestEntryPoint());
digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
return digestAuthenticationFilter;
}
#Bean
public DigestAuthenticationEntryPoint digestEntryPoint()
{
DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
digestAuthenticationEntryPoint.setKey("mykey");
digestAuthenticationEntryPoint.setRealmName("myrealm");
return digestAuthenticationEntryPoint;
}
}
I am attempting to authorize on the client side by including the header:
Authorization: Digest username="user", realm="myrealm", nonce="", uri="/service?param=98", response="fcd46faf42a583499d4e7f0371171ef2", opaque=""
I am able to access the intended services if I revert this class to a HttpBasic based configuration. Is the problem with my config or with my request? Most of the above code was borrowed from another post, however I cannot get things working in this context. All of this is running within Spring Boot 0.5.0M7.
Thanks.
The request seems to be incomplete. The noonce parameter should contain a base64 encoded value according to the digest processing filter reference.
Central to Digest Authentication is a "nonce". This is a value the server generates. Spring Security’s nonce adopts the following format:
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
Spring and Patrick both describe a flow where a request is made, if nothing else to get a nonce from the server , the server provides this header
"WWW-Authenticate: Digest realm="realm", nonce="IVjZjc3Yg==", qop="auth"
in its 401 response saying "hey who are you" to the client. Using the nonce and other stuff a md5 hash is created and sent to the server. Server is now happy and processes the request. Look on the bright side you made it to step 1 and check the links for a better explaination
Related
I am trying to build an OAuth2 Client using spring boot to access a custom API.
Here is my code so far:
#SpringBootApplication
public class MyClient {
public static void main(String[] args){
SpringApplication.run(MyClient.class, args);
}
}
#Configuration
#EnableOAuth2Sso
public class ApplicationSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.antMatcher("/**")
.authorizeRequests().antMatchers("/","/callback","/login**","/error**")
.permitAll().anyRequest().authenticated();
}
}
#RestController
public class HomeController {
#RequestMapping("/user")
public String login() {
String userInfoJson = "get data returned from API somehow";
return userInfoJson;
}
#RequestMapping("/callback")
public void callback(HttpServletResponse response) {
response.sendRedirect("/user");
}
}
I have created an application.yml file with all the properties needed and the login process works perfectly, returning the flow to /callback after a successful login.
At that point I should have received a token that I can use to fetch the user's data from the server.
How can I access this token?
Also, does spring boot have any classes to do the token validation process automatically, or do I have to create the request manually?
Thanks
from the documentation
An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.
The Resource Server above support this via the user-info-uri property This is the basis for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to participate by providing an annotation #EnableOAuth2Sso.
The Github client above can protect all its resources and authenticate using the Github /user/ endpoint, by adding that annotation and declaring where to find the endpoint (in addition to the security.oauth2.client.* configuration already listed above):
application.yml.
security:
oauth2:
...
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
So as you can read this should be done automatically if you set where the user info shall be fetched from, and then it will be populated.
When an Authentication token is populated you can fetch this from the security context in multiple different ways.
#RequestMapping("/callback")
public void callback(Authentication authentication) {
final String name = authentication.getName();
response.sendRedirect("/user");
}
If you wish to access the raw json, you'll probably have to make the rest call yourself. If you want to add custom values to the authentication object you have to implement your own UserDetails class.
I have built a basic crud spring mvc rest api and would like to add basic authentication for validating the user and adding login and logout. I have found a tutorial which covers spring mvc.
I have tried implementing the basic authentication and the code is as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
auth.inMemoryAuthentication()
.withUser(users.username("marie").password("yourmarie").roles("USER"));
}
With the above code it provides authentication but when the credentials are invalid it returns an exception. Instead, I would like some sort of jason or message to be returned if possible so that I can process actions in the front end based off the returned value.
One the other hand if I try to access the endpoints without authentication, it gives me a login form. Here, I would like a response too.
Also, would like to add logout functionality as well.
Please guide.
Thanks in advance!
If you found an exception, while invalid credentials, then simply catch the exception in your controller. and create your own authentication exception and throw it with proper message.
catch (Exception ae) { throw new AuthenticationException("Invalid
credentials"); }
For logout you need to remove the token from the storage(memory) You can check token in request as below if
(StringUtils.hasText(httpServletRequest.getHeader("Authorization"))) {
String access_token = request.getHeader("Authorization"); }
https://www.baeldung.com/java-config-spring-security
I have a spring-security-oauth2 project running smoothly with a class as Authorization server.
The client-ids, user-tokens, refresh-tokens are all managed by the database.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static String REALM = "MY_OAUTH_REALM";
...
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm(REALM + "/client");
}
}
Everything is working fine except that i don't have any idea what the configure method is doing. Even if i remove the complete method the oauth2 process still works fine.
What is the main use of configure method in this context and what realm is it setting here?
Please help me in understanding it.
Thanks.
Purpose of configure method
AuthorizationServerConfigurerAdapter has three configure(...) methods and all three could be overridden and those serve different purposes.
In your question, you have quoted only one.
Their purpose is to provide your custom settings for Authorization Server end points, clients & security. So its up to you as how many you wish to override as there are some predefined default settings.
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// This can be used to configure security of your authorization server itself
// i.e. which user can generate tokens , changing default realm etc.
// Sample code below.
// We're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority.
// There are few more configurations and changing default realm is one of those
oauthServer
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Here you will specify about `ClientDetailsService`
// i.e. information about OAuth2 clients & where their info is located - memory , DB , LDAP etc.
// Sample code below.
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// This can be used to configure security of your authorization server itself
// i.e. which user can generate tokens , changing default realm etc - Sample code below.
// we're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority.
// There are few more configurations and changing default realm is one of those
oauthServer
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Here you will specify about `ClientDetailsService` i.e.
// information about OAuth2 clients & where their info is located - memory , DB , LDAP etc.
// Sample code below
clients.inMemory()
.withClient("trusted-app")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds("oauth2_id")
.accessTokenValiditySeconds(10000)
.refreshTokenValiditySeconds(20000)
.secret("secret");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Here you will do non-security configs for end points associated with your Authorization Server
// and can specify details about authentication manager, token generation etc. Sample code below
endpoints
.authenticationManager(this.authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
Purpose of #EnableAuthorizationServer
A javadoc explanation is already provided in previous answer.
In layman's language, this is to set up your token generation end point i.e.
if you provide the properties security.oauth2.client.client-id and security.oauth2.client.client-secret, Spring will give you an authentication server, providing standard Oauth2 tokens at the endpoint /oauth/token
In practical scenario, what this means is that you are setting up a token generation web-application ( Layer-7 ) on top of your enterprise User LDAP or User Database and is usually a separate application from your consumer side apps ( APIs etc ).
If you have a look at the JavaDoc comment for #EnableAuthorizationServer you can see that it says the following;
Convenience annotation for enabling an Authorization Server (i.e. an
AuthorizationEndpoint and a TokenEndpoint in the current application
context, which must be a DispatcherServlet context. Many features of
the server can be customized using #Beans of type
AuthorizationServerConfigurer (e.g. by extending
AuthorizationServerConfigurerAdapter. The user is responsible for
securing the Authorization Endpoint (/oauth/authorize) using normal
Spring Security features (EnableWebSecurity #EnableWebSecurity
etc.), but the Token Endpoint (/oauth/token) will be automatically
secured using HTTP Basic authentication on the client's credentials.
Clients must be registered by providing a ClientDetailsService through
one or more AuthorizationServerConfigurers.
Extending AuthorizationServerConfigurerAdapter is just used for customization of the Authorization Server. You can easily set up a functioning Authorization Server within Spring Security by Just Annotating a Bean class with #EnableAuthorizationServer
I have 2 apps running, one is resource server where I have the info that needs authentication to view the text. Then I have authorization server that gives tokens. Right now I can use postman or Insomnia, add the auth_url, token_url, client_id, client_secret and I get the token. I add the token to header and i get do a get request to my resource server using header, and it works just fine.
Now i have no idea how to implement redirection from my resource server directly. Like when I go to
localhost:9000/home
I'd like to get redirected to:
localhost:9001/login
where I login with my inmemory user then it redirects me back to localhost:9000/home and I see the message.
What would be the best way to implement a way for user to access information on localhost:9000/home. You go to localhost:9000/home, it goes to authorization server on localhost:9001, you log in with username and password. Approve the grant, and it puts you back to localhost:9000/home and then you can see the text, what was previously protected, because you didn't have token to access it.
ResourceServer.java
#SpringBootApplication
#RestController
#EnableResourceServer
#EnableOAuth2Client
public class SampleResourceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
public static void main(String[] args) {
SpringApplication.run(SampleResourceApplication.class, args);
}
#RequestMapping("/home")
public String home() {
return "this is home";
}
}
and my properties looks like:
server:
port: 900
security:
oauth2:
client:
client-id: foo
client-secret: foosecret
access-token-uri: http://localhost:9001/auth/oauth/token
user-authorization-uri: http://localhost:9001/auth/oauth/authorize
grant-type: USER
auto-approve-scopes: true
resource:
user-info-uri: http://localhost:9001/auth/user
Let's separate the agents: You have the user (i.e. you, also know as the resource owner), the authorization server, the resource server and the client (the application that access your urls, i.e. your browser).
Normally, this happens in your situation:
When your client access the resource server, it receives a 401. Depending of your implementation, you could also directly redirect the client to your AS (using a simple redirect response).
Your AS prompts you for credentials. After validating them, it issues a token for you. You can then use this token to access the RS.
What you're trying to get (if I understand correctly) is to redirect with the token automatically. To achieve this, you can simply pass the url you tried to reach (i.e. localhost:9000/home) when you redirect to your AS at the end of step 1. Your AS hten prompts the user for credentials, generate the token, stores it as a cookie (in the case of a browser), and redirects you to the url he received (localhost:9000/home).
EDIT: what's the resulting code for the redirection.
When you get to the configure, you first check if the user is authenticated. If he is, then all's fine, but if he isn't, you must catch this event and start your redirection. This can be done using the exceptionHandling method of the chaining http:
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
// You can use a lambda here
#Override
public void commence(HttpServletRequest aRequest, HttpServletResponse aResponse,
AuthenticationException aAuthException) throws IOException, ServletException {
aResponse.sendRedirect(MY_AS_URL + "?redirect_uri=localhost:9001/home");
}
};
}
Unfortunately, I am not familiar with the Spring framework, but hopefully this helps anyways:
OAuth is an authorization protocol. It does not handle authentication (see also: "What is the difference between authentication and authorization?" on ServerFault).
If I understand you correctly, you want users to be redirected to /login when they go to /home and aren't already logged-in. This step has nothing to do with OAuth, it must be part of your application's security / firewall setup.
Please also note that there is a difference between logging in (authenticating) on the authorization server and actually granting the application the right to access your resources (authorization). First you have to prove who you are and only then can you give access to your stuff. These are two separate steps.
I am just a beginner in Spring Security Oauth2.
I try to make Authorization Server and Resource Server (separated and connect to JDBC) and the purpose is to make Single Sign-On.
My flow success to get accesstoken and refreshtoken from Authorization Server. My accesstoken always used as parameter to access Resouce Server, and this is give a response back.
for example http://127.0.0.1:8080/Resource/res/staff?access_token=xxxxxxxxxxxxxxxxxxxxx
My problem, if the accesstoken expired, the spring security will prevent to access the page and give the error exception. When I must use the refreshtoken to get new token?
Or is my flow wrong? Is there other flow to renew accesstoken?
Thanks
Edited:
FYI:
I want to make SSO using Spring Security Oauth2. I have several Apps Server (use Spring Framework) and I want to make one server that responsible to manage the login. And I want to make the Apps Server become Resource Server (also the Client)
So I make one Authorization Server with Spring Security Oauth2. The user who wants to access the protected Resource Server must login to Authorization Server (the Resource Server authorize to Authorization Server). It will get a code and then the Resource Server will exchange this code with accessToken and refreshToken. This flow is success.
I can also request the new accessToken using the refreshToken that given by Authorization Server.
But I cannot call this procedure because if I access the url mapping, previously the spring security was blocking the access and give the invalid token error return.
How can I solve the missing link?
Updated:
This is my Authorization Server Configuration:
#Configuration
public class Oauth2AuthorizationServer {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(new JdbcTokenStore(dataSource))
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || permitAll()").checkTokenAccess("permitAll()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
And this is my Resource Server Configuration (as Client too)
#Configuration
public class Oauth2ResourceServer {
private static final String RESOURCE_ID = "test";
#Configuration #Order(10)
protected static class NonOauthResources extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/halo").permitAll()
.antMatchers("/api/state/**").permitAll()
.antMatchers("/**").permitAll()
.and().anonymous();
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId("jsclient");
tokenService.setClientSecret("jspasswd");
tokenService.setCheckTokenEndpointUrl("http://localhost:8084/Server2Auth/oauth/check_token");
resources
.resourceId(RESOURCE_ID)
.tokenServices(tokenService);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.filterSecurityInterceptorOncePerRequest(true)
.antMatchers("/res/staff").hasRole("STAFF")
.antMatchers("/res/client").access("#oauth2.hasScope('trust')")
.antMatchers("/res/admin").hasRole("ADMIN")
.and()
.exceptionHandling().accessDeniedPage("/403");
}
}
}
Resource (as Client too) request to authorize:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
Feedback from Authorize Server:
http://localhost:10001/resource-server/api/state/new?code=8OppiR
Resource (as Client) exchange the code to Authorize Server:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -H "Accept: application/json" -d "grant_type=authorization_code&code=iMAtdP&redirect_uri=http://localhost:10001/resource-server/api/state/new"
Feedback from Authorize Server:
{
"access_token":"08664d93-41e3-473c-b5d2-f2b30afe7053",
"token_type":"bearer",
"refresh_token":"436761f1-2f26-412b-ab0f-bbf2cd7459c4",
"expires_in":43199,
"scope":"write read"
}
Resource (as Client) access the url itself
curl http://localhost:10001/resource-server/api/admin?access_token=08664d93-41e3-473c-b5d2-f2b30afe7053
Request new access toke using refresh token
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
The OAuth2 Spec has a section on refreshing access tokens. It's implemented in a pretty standard way in Spring OAuth (you just post the refresh token to the /token endpoint).
BTW for SSO you don't normally need a Resource Server. But that's a different question.