I have to integrate my system with third-party provider. This system is made with Spring and Angular.
Keep in mind that I need to create a custom login form instead redirecting to thirdy-party provider form like OAuth2.
He has created following endpoints:
Get token authentication
POST http://example.com/webapi/api/web/token
“username=972.344.780-00&password=123456&grant_type=password”
The response send me a token that I must use during all next requests.
Get user info
Authorization: Bearer V4SQRUucwbtxbt4lP2Ot_LpkpBUUAl5guvxAHXh7oJpyTCGcXVTT-yKbPrPDU9QII43RWt6zKcF5m0HAUSLSlrcyzOuJE7Bjgk48enIoawef5IyGhM_PUkMVmmdMg_1IdIb3Glipx88yZn3AWaneoWPIYI1yqZ9fYaxA-_QGP17Q-H2NZWCn2lfF57aHz8evrRXNt_tpOj_nPwwF5r86crEFoDTewmYhVREMQQjxo80
GET http://example.com/webapi/api/web/userInfo
That said, What I need to implement a custom authentication?
Could I use Spring OAuth2 in this case?
you can use Spring Security. The flow is the following. You authenticate against the Security token service. A cookie containing the authentication token is written to your browser. This token is sent on each subsequent request against the server.
On the rest server you will use Srping Security and more specifily you need to use AbstractPreAuthenticatedProcessingFilter in its implementation you will extract the token and associate it With the Security Context.
Here is example configuration of your spring Security
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
public void configure(WebSecurity web) throws Exception {
// do some configuration here
}
#Override
public void configure(HttpSecurity http) throws Exception {
// configure your Security here
// you can add your implementation of AbstractPreAuthenticatedProcessingFilter here
}
}
Here is your additional configuration
#Configuration
public class ExampleSpringSecurityConfig{
#Bean
public AuthenticationManager authenticationManager() {
return authentication -> authProvider().authenticate(authentication);
}
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userdetailsService() {
// Construct your AuthenticationUserDetailsService here
}
#Bean
public PreAuthenticatedAuthenticationProvider authProvider() {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userdetailsService());
return authProvider;
}
}
Yes, you can use Spring Oauth2. You have to implement the Resource Owner Password Credentials Grant Oauth2 flow.
You have to create a login page for end user and your client app will send the user's credentials as well as your client system credentials (use HTTP Basic Authentication for client system credentials) to authorization server to get the token.
There are two ways to implement it-
Using client system id and password - When calling the token endpoint using the this grant type, you need to pass in the client ID and secret (using basic auth).
curl -u 972.344.780-00:123456 "http://example.com/webapi/api/web/token?grant_type=password&username=addEndUserNameHere&password=addEndUserPasswordHere"
Using Client system ID only (no client system password) - Authorization Server should have a client setup to support this flow without any password-
Child class of AuthorizationServerConfigurerAdapter should have below code-
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientId")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT")
.scopes("read");
}
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
Now you can use below-
POST http://example.com/webapi/api/web/token?grant_type=password&client_id=my-trusted-client&scope=trust&username=addEndUserNameHere&password=addEndUserPasswordHere
Note - This flow is less secure than other Oauth2 flows and recommended for trusted client app only because user has to provide credentials to client app.
See here example
Using JWT with Spring Security OAuth2 with Angular
In this tutorial, we’ll discuss how to get our Spring Security OAuth2 implementation to make use of JSON Web Tokens.
http://www.baeldung.com/spring-security-oauth-jwt
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}
Related
I'd like to secure my app using Spring Security's Resource Server and Authorization Server included in my component.
The desired flow include using only client-credentials grant type and passing client_id together with client_secret as base64 header, what should return token for further requests after hitting oauth/token endpoint. I also include grant_type: client-credentials in POST request parameters
For now I am receiving error:
"Full authentication is required to access this resource".
The strange thing is despite my configuration Spring still generates random security password what can be seen in console log.
This is my first approach to Spring Security so maybe I've missed something?
Below is my configuration:
AuthorizationServerConfig:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#Override
public void configure(final AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("some-client")
.authorizedGrantTypes("client-credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(3600)
.secret("somePass")
.refreshTokenValiditySeconds(24*3600);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
ResourceServerConfig:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/token", "/oauth/authorize", "/oauth/confirm_access").permitAll()
.antMatchers("/**").authenticated()
.and().httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
I am using Spring Boot 2.0.1.RELEASE and Spring Security OAuth2 2.0.14.RELEASE.
As in my case, InMemoryTokenStore is used it will work with one instance, what is the best substitute for this if one wanted to create multiple instances of app?
When you are storing the users in memory, you are providing the passwords in plain text and then when you are trying to retrieve the encoder from the DelegatingPasswordEncoder to validate the password it can't find one.
You can try out the following changes so that you can override password encoding by adding {noop}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("some-client")
.authorizedGrantTypes("client-credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(3600)
.secret("{noop}somePass") //change here
.refreshTokenValiditySeconds(24*3600);
}
And also change your user in WebSecurityConfigurer doing the same for the password.
I am in a real problem here and would require your help. I work for a bank and have been assigned a task of implementing an OAuth2 service using Spring Boot, I have been exploring since last one week and have been able to implement a password flow grant type OAuth2 service, but now I have a few questions as my seniors said that password flow is not the right fit for our use case. First I would like to explain the use case:
Step 1: user will hit the application URL of the web app where no login is needed and before the application loads the OAuth2 service will be hit with logged in AD (system) user id.
Step 2. The OAuth2 service should authenticate the user using ldap with the given user id and return back all groups the user is part of along with the access token which will be used to access the API's there after
Now I have the below queries:
Which grant type is best suited for my need, from what I have read authorization code grant type seems to be the right fit? Or is it implicit?
Depending on the answer for question 1 what code changes do I need to make in the below code:
Code snippet for my authorization server:
Oauth2AuthserverApplication.java
#SpringBootApplication
#EnableAuthorizationServer
public class Oauth2AuthserverApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2AuthserverApplication.class, args);
}
}
OAuth2Congig.java
#Configuration
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private String clientId = "client";
private String clientSecret = "secret";
private String privateKey = "private-key";
private String publicKey = "public-key";
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter tokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenEnhancer());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).
tokenStore(tokenStore())
.accessTokenConverter(tokenEnhancer());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.tokenKeyAccess("permitAll()").
checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws
Exception {
clients.inMemory().withClient(clientId).
secret(clientSecret).scopes("read", "write")
.authorizedGrantTypes("password",
"refresh_token").accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomDetailsService customDetailsService;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws
Exception {
auth.userDetailsService(customDetailsService).
passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().
and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
}
Not pasting the model, dao, and service code of the authorization server class as they are not relevant for this question.
Code Snippet from resource server project:
OAuth2ResourceserverApplication.java
#SpringBootApplication
#EnableResourceServer
#RestController
public class Oauth2ResourceserverApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ResourceserverApplication.class, args);
}
#RequestMapping(value="/api")
public String success() {
return "SUCCESS";
}
}
JwtConverter.java
#Component
public class JwtConverter extends DefaultAccessTokenConverter implements
JwtAccessTokenConverterConfigurer {
#Override
public void configure(JwtAccessTokenConverter converter) {
converter.setAccessTokenConverter(this);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().
and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
application.yml
server:
port: 8081
security:
oauth2:
resource:
filter-order: 3
jwt:
key-value: private-key
OAuth2 has 4 grant types. To quickly understand the difference between 'Resource Owner Password Credential', 'Authorization Code', 'Implicit', let us compare them side-by-side:
NOTE: The full explanation is available at: https://blog.oauth.io/oauth2-flow-grant-types-in-pictures/.
To answer your question:
Which grant type is best suited for my need, from what I have read authorization code grant type seems to be the right fit? Or is it implicit?
If you compare based on 'Security' the purple bar, 'Authorization Code' is best. However you can see that it has the concept of Guard (backend) that performs access to the User data store on behalf of the App (frontend), i,.e., the App never has access to the Key/Access Token directly, as the Key is retrieved through the exchange of username/password between the User and the OAuth Server, and then passed on to the Guard.
The 'Resource Owner Password Credential' that you implemented is the least secure as the username/password is handed over to the App for the App to do everything that the User can without the User's further consent. However, in your scenario, the App and the User data store both belong to you so that alleviates the security issue.
Depending on the answer for question 1 what code changes do I need to make in the below code:
The full flow of Resource Owner Password Credential grant type you implemented is the left part of the image below and the Authorization Code grant type is the right part. As you can see, there are generally 5 steps. For Resource Owner Password Credential, some steps are not necessary, i.e., marked 'N.A.'.
NOTE:
The 'cloud' represents the App
The 'www' represents the User/Browser
The 'safe' represents the OAuth Server
To get from the left-side to the right-side, changes you required will be:
Step 1. If your OAuth Server is going to support different Apps, then it needs to support App pre-registration to get client id/secret. If you only have one App then you can skip this.
Step 2. Instead of the App prompting for username/password now the App will redirect the User to the OAuth Server to perform username/password authentication
Step 3. After authenticating the User the OAuth Server can prompt the User about what kind of permissions (e.g., read email, update profile, etc.) she wants to grant the App
Step 4. The OAuth Server instead of handing the Key/Access token to the App, it hands a code to User which then passed it to the App
Step 5. The App then exchanges the code for the Key/Access Token with the OAuth Server.
Once you get the Key/Access Token you can call any protected API on a different server which can then validate the Key/Access Token with the OAuth Server before responding to the API request, e.g., return the groups a User belongs to.
I have a Spring Security AuthorizationServerConfigurerAdapter configuration which is supporting password and refresh_token grant types.
clients
.inMemory()
.authorizedGrantTypes("password", "refresh_token")
...;
The TokenStore i am using is JwtTokenStore, thus the refresh_token and access_token are generated as JWT as i am using the DefaultTokenServices
The question is how can i have the refresh_token generated and managed by JdbcTokenStore while the access_token still get generated and managed by JwtTokenStore?
I have thought about extending DefaultTokenServices or implementing AuthorizationServerTokenServices but i'm not sure if there is not any other way offered by the default spring-secuirty config.
Thanks!
One way, to achieve stored tokens (both access token and refresh token) and have JWT encoded tokens at the same time, is provide token store with tokenEnhancer of type JwtAccessTokenConverter.
#Bean
protected TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("client_trusted")//...
;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.tokenEnhancer(jwtTokenEnhancer()) // <- tokens are encoded in JWT
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
With this approach you can easily revoke (or remove) refresh_token. So authorization server will not provide new access token in next refreshing token request. And information in JWT stays self contained and resource server can work without interaction with authorization server.
#Autowired
protected TokenStore tokenStore;
#RequestMapping(method = RequestMethod.POST, value = "/revoke")
public void revokeToken(#RequestParam String token) {
((InMemoryTokenStore)tokenStore).removeRefreshToken(token);
}
Here is complete example of authorization and resource server with js client: https://github.com/pufface/spring-oauth-jwt-demo
I have a REST service that uses OAuth2 authentication and that provides an endpoint to request a token with the client_credentials grant type. The application is based on Spring Boot.
So far I figured out I can request a token with something like:
#SpringBootApplication
#EnableOAuth2Client
public class App extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
//...
#Override
protected void configure(HttpSecurity http) throws Exception {
// Does nothing - to allow unrestricted access
}
#Bean
protected OAuth2RestTemplate myTemplate() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri("http://localhost:8080/oauth/token");
details.setClientId("theClient");
details.setClientSecret("thePassword");
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
}
#RestController
public class TestController {
#Autowired
OAuth2RestTemplate myTemplate;
#RequestMapping("/token")
private String getToken() {
return myTemplate.getAccessToken().getValue();
}
}
And it almost works, but whenever I call the /token endpoint, there's an exception:
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:88) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
...
The exception is thrown here, but I'm not sure how I can make Spring use context authentication other than AnonymousAuthenticationToken. In fact, I don't want any authentication from the client, because anonymous is perfectly okay. How can I achieve this?
I have two different types of users.
SSO users
DB users.
SSO users would have already been authenticated by different system and DB users should be authenticated by our system. Can i configure Spring security to handle this scenario where by i can say prompt login page for some users and don't prompt for some.
Lets say for SSO users i can get there users ID in request headers while DB when they access the application there is no user id present in request header .How can i handle this scenario ?
Can i override authenticate method of DaoAuthenticationProvider by extending it and then page on some parameter decide to authenticate user or is there any other means ? can i add any information to Authentication class
This is What i have tried to Far
Security Config java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
.usersByUsernameQuery("select username,password, enabled from users where username=?")
.authoritiesByUsernameQuery("select username, role from user_roles where username=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and().addFilterBefore(new UserTypeFilter(), BasicAuthenticationFilter.class);
}
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
/*
* #Bean public MethodSecurityInterceptor methodSecurityService() { return
* new MethodSecurityInterceptor(); }
*/
#Bean(name="myAuthenticationManager")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public ExceptionTranslationFilter exceptionTranslationFilter() {
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
new Http403ForbiddenEntryPoint());
AccessDeniedHandlerImpl accessDeniedHandlerImpl = new AccessDeniedHandlerImpl();
accessDeniedHandlerImpl.setErrorPage("/exception");
exceptionTranslationFilter
.setAccessDeniedHandler(accessDeniedHandlerImpl);
exceptionTranslationFilter.afterPropertiesSet();
return exceptionTranslationFilter;
}
#Bean
public UserTypeFilter authenticationFilter() throws Exception {
UserTypeFilter authFilter = new UserTypeFilter();
authFilter.setAuthenticationManager(authenticationManager());
return authFilter;
}
}
My Custom AbstractAuthenticationProcessingFilter
public class UserTypeFilter extends AbstractAuthenticationProcessingFilter {
private static final String INTERCEPTOR_PROCESS_URL = "/index";
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
public UserTypeFilter() {
super(INTERCEPTOR_PROCESS_URL);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
System.out.println(" BASIC AUTHENTICATION FILTER");
String userId = request.getHeader("USERID");
if (userId == null) {
System.out.println(" THROWING EXCEPTION FILTER");
throw new PreAuthenticatedCredentialsNotFoundException("USERID param not found");
}
return null;
}
}
My Controller
#Controller
public class MainController {
#RequestMapping(value = { "/index" }, method = RequestMethod.GET)
public ModelAndView index() {
ModelAndView model = new ModelAndView();
model.addObject("message", "This is test page!");
model.setViewName("dummy");
return model;
}
}
The control goes to My Custom filter and then when the exception is thrown but ExceptionTranslationFilter is not getting called
Have i configured httpsecurity correctly
Have i configured My custom filter correctly
Have i configured ExceptionTranslation Filter
Am i missing anything
This is a pretty standard use case for spring security. You will need to provide an Authentication object into the security context before any security interceptor is encountered.
Typically you would have some kind of filter which extracted SSO parameters from the request, authenticated those parameters against the SSO service, and then create an Authentication object and put it into the security context. The type of filter and configuration of the filter will depend on what SSO technology you are using.
There would often also be a filter (usually an ExceptionTranslationFilter) which will send unauthenticated requests to a login page.
There would also be filters to receive the parameters from the login form and store them in the security context.
Putting it all together I would expect one possible workflow to be:
User logs in with SSO parameters
Request comes in prepopulated with credentials
Some filter extracts those credentials, verifies them, creates an Authentication object, places the object in the security context.
The security interceptor finds the Authentication object in the security context, verifies the user is allowed access to the particular function, and passes the request on.
User logs in without SSO parameters (needs login page)
Request comes in with no credentials
The security interceptor finds no Authentication object and throws an exception.
The ExceptionTranslationFilter turns the exception into a redirect to a login page.
User logs in with filled out login form (e.g. for DB login)
Request comes in with a login form as the entity body
Some filter (e.g. UsernamePasswordAuthenticationFilter) extracts the credentials from the login form and defers to an authentication provider (e.g. your DAO authentication provider) to query the database and verify the user. If verified this filter will create an Authentication object and place it in the security context.
The security interceptor finds the Authentication object in the security context, verifies the user is allowed access to the particular function, and passes the request on.