I am trying to implement Spring Security OAuth2 using Java config.
My usecase requires the use of password grant_type.
I have configured this so far without the need for a web.xml and would prefer to keep it that way
Versions I am using:
Spring Framework: 4.1.6
Spring Security: 4.0.1
Spring Security OAuth:2.0.7
To make explaining this easier I have enabled GET on the token endpoint
#Override
public void configure
(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
{
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET); //<-- Enable GET
}
The request that I am making is as follows:
http://localhost:8080/project/oauth/token?
client_id=testClient&
grant_type=password&
username=user&
password=password
The header includes an Authorization header that contains the encoded version of:
Username: user
Password: password
The exception I get is:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.security.oauth2.provider.NoSuchClientException:
No client with requested id: user
From the exception description it appears that OAuth is looking in the ClientDetailsService for the client: user. However user is a user credential. I am obviously missunderstanding something about the configuration.
My configuration is as follows;
ServletInitializer.java
public class ServletInitializer extends AbstractDispatcherServletInitializer {
#Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.scan(ClassUtils.getPackageName(getClass()));
return context;
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException{
super.onStartup(servletContext);
DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
filter.setContextAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher");
servletContext.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
}
}
WebMvcConfig.java
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception{
auth.
inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/Services/*")
.authenticated()
.and()
.httpBasic();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OAuth2ServerConfig.java
#Configuration
public class OAuth2ServerConfig {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter{
#Autowired
private TokenStore tokenStore;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients
.inMemory()
.withClient("testClient")
.secret("secret")
.scopes("read", "write")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(3600);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.resourceId("SomeResourseId").stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception{
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/secure/**").access("#oauth2.hasScope('read')");
}
}
}
Code in gitrepo for ease of access: https://github.com/dooffas/springOauth2
I'm not sure where the 500 comes from in your case. I see a 406 because there is no JSON converter for the access token (Spring used to register one by default for Jackson 1.* but now it only does it for Jackson 2.*). You token endpoint works for me if I add jackson-databind to the classpath, e.g.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.4</version>
<scope>runtime</scope>
</dependency>
This works for me:
$ curl -v testClient:secret#localhost:8080/oauth/token?'grant_type=password&username=user&password=password'
P.S. you really ought not to use GET for a token request.
You have defined different authorities
try this:
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception{
auth.
inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER", "CLIENT");
}
And add param grant_type=password to your request
Related
This question already has an answer here:
Spring Security Authentication issue: HTTP 401
(1 answer)
Closed 4 years ago.
I'm trying to implement SSO using Spring OAuth lib for learning purpose.
The AuthenticationServer looks like this:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() //
.withClient("acme") //
.secret("acmesecret") //
.authorizedGrantTypes("authorization_code", "refresh_token", "password") //
.scopes("openid");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
}
WebSecurity like this:
#Configuration
#EnableWebSecurity
public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login").permitAll().and() //
.logout().and() //
.authorizeRequests().anyRequest().hasRole("USER");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
and RestController:
#RestController
#EnableResourceServer
public class UserController {
#GetMapping("/user/me")
public Principal user(Principal principal) {
return principal;
}
}
The web application is kept minimal:
#SpringBootApplication
#EnableOAuth2Sso
public class WebApplication implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
application.yml:
spring:
thymeleaf:
cache: false
security:
basic:
enabled: false
oauth2:
client:
clientId: acme
clientSecret: acmesecret
accessTokenUri: http://localhost:9999/auth/oauth/token
userAuthorizationUri: http://localhost:9999/auth/oauth/authorize
resource:
userInfoUri: http://localhost:9999/auth/user/me
when I enter the URL localhost:8080 I should be redirect to the login (generated by spring?), but I get a 401 error.
2018-09-29 12:42:28.257 DEBUG 7677 --- [nio-9999-exec-2] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
What am I missing?
Thank you!
You need to set web security configuration in the web application.
The reason why you get 401 is even the login url is not allowed to be connected by Spring Security.
So basically you need to add a java config like the following
#Configuration
#EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
Hope this helps! Happy Coding :)
I tried to make an Oauth2 server. It works fine, until I turn on form login. When I do, it redirects me to the login form, even though my request contains the access token.
Authorization server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAnonymous() || hasRole('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasRole('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials,", "password", "authorization_code")
.authorities("USER")
.scopes("read", "write", "trust")
.resourceIds("oauth2-resource")
.accessTokenValiditySeconds(3600)
.secret("secret")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
WebSecurity:
#Configuration
public class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.httpBasic().disable()
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated();
}
}
Resource server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/me").authorizeRequests().anyRequest().authenticated();
}
}
User info:
#RestController
public class UserController {
#RequestMapping(value ="/me", produces = "application/json")
public ResponseEntity<Map<String, String>> user(Principal principal) {
HashMap<String, String> result = new HashMap<>();
result.put("username", principal.getName());
return ResponseEntity.ok(result);
}
}
The app:
#SpringBootApplication
#EnableAuthorizationServer
public class Security1Application {
public static void main(String[] args) {
SpringApplication.run(Security1Application.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder authenticationManagerBuilder, UserService userService) throws Exception {
authenticationManagerBuilder.userDetailsService(userService);
}
}
I also have a user service that returns UserDetails.
When I try it from browser, it works fine. I can login and then acces the restricted /me endpoint. But if I get an access token from the token endpoint and try to use it to access /me I get redirected to the login form. If I remove the .formLogin(), then the token works, but on the website I have to login in a popup alert window, which is not very nice.
I want to use OAuth2 for my REST spring boot project. Using some examples I have created configuration for OAuth2:
#Configuration
public class OAuth2Configuration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password", "refresh_token", "trust")
.authorities("USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("clientsecret")
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(3600);
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
This is my SecurityConfiguration class:
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.authorizeRequests().antMatchers("/api/register").permitAll()
.and()
.authorizeRequests().antMatchers("/api/free").permitAll()
.and()
.authorizeRequests().antMatchers("/oauth/token").permitAll()
.and()
.authorizeRequests().antMatchers("/api/secured").hasRole("USER")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I tried to check my application with 2 simple requests:
#RequestMapping(value = "/api/secured", method = RequestMethod.GET)
public String checkSecured(){
return "Authorization is ok";
}
#RequestMapping(value = "/api/free", method = RequestMethod.GET)
public String checkFree(){
return "Free from authorization";
}
Firstly I checked two requests:
/api/free returned code 200 and the string "Free from authorization"
/api/secured returned {"timestamp":1487451065106,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/secured"}
And it seems that they work fine.
Then I got access_token (using credentials from my users database)
/oauth/token?grant_type=password&username=emaila&password=emailo
Response:
{"access_token":"3344669f-c66c-4161-9516-d7e2f31a32e8","token_type":"bearer","refresh_token":"c71c17e4-45ba-458c-9d98-574de33d1859","expires_in":1199,"scope":"read write"}
Then I tried to send a request (with the token I got) for resource which requires authentication:
/api/secured?access_token=3344669f-c66c-4161-9516-d7e2f31a32e8
Here is response:
{"timestamp":1487451630224,"status":403,"error":"Forbidden","message":"Access Denied","path":"/api/secured"}
I cannot understand why access is denied. I am not sure in configurations and it seems that they are incorrect. Also I still do not clearly understand relationships of methods configure(HttpSecurity http) in class which extends WebSecurityConfigurerAdapter and in another which extends ResourceServerConfigurerAdapter.
Thank you for any help!
If you are using spring boot 1.5.1 or recently updated to it, note that they changed the filter order for spring security oauth2 (Spring Boot 1.5 Release Notes).
According to the release notes, try to add the following property to application.properties/yml, after doing that the resource server filters will be used after your other filters as a fallback - this should cause the authorization to be accepted before falling to the resource server:
security.oauth2.resource.filter-order = 3
You can find a good answer for your other questions here: https://stackoverflow.com/questions/28537181
This configuration works with boot 1.4.3.RELEASE
my static content works perfectly.
When i change the version to 1.5.1.RELEASE my static content returns
404 not found.
#Configuration
#EnableWebMvc
public class AppWebConfiguration extends WebMvcConfigurerAdapter{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
registry.addResourceHandler("/resources/**").addResourceLocations("file:" + Paths.get(System.getProperty("user.home"), SGISSConfig.getInstance().getRepoFolder()) + "/");
}
Security Class
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/email/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll();
}
#Autowired
private UserDetailsService users;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(users).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception{
web.ignoring().antMatchers("/static/**");
}
}
My static resources are /resources/static/**
Edit
Folder structure
Working on implementing Oauth2 with Spring. I want to implement the implicit workflow:
My configuration file:
#Configuration
#EnableAutoConfiguration
#RestController
public class App {
#Autowired
private DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#RequestMapping("/")
public String home() {
return "Hello World";
}
#Configuration
#EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().antMatchers("/oauth/token").authenticated()
.and()
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.csrf().disable();
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager auth;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
#Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(DBConnector.dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(DBConnector.dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(auth).tokenStore(tokenStore())
.approvalStoreDisabled();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.jdbc(DBConnector.dataSource)
.passwordEncoder(passwordEncoder)
.withClient("my-trusted-client")
.secret("test")
.authorizedGrantTypes("password", "authorization_code",
"refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.resourceIds("oauth2-resource")
.accessTokenValiditySeconds(0);
// #formatter:on
}
}
#Autowired
public void init(AuthenticationManagerBuilder auth) throws Exception {
// #formatter:off
auth.jdbcAuthentication().dataSource(DBConnector.dataSource).withUser("dave")
.password("secret").roles("USER");
// #formatter:on
}
}
This is working so far. A user is also generated in the database.
Problem is following. When i try to do following request:
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=my-trusted-client&username=dave&password=secret
I always get a popup window (authentication) asking me to enter a username and a password. But it doesn't matter what i enter there i never pass through. So what is wrong there?
I would like to have it, that when i call this url, that i get back my access_token.
In case of implicit flow all token will be generated through authorization url instead of token url. so you should hit ../oauth/authorize endpoint with implicit response type. i.e
../oauth/authorize?response_type=implicit&client_id=trusted_client&redirect_uri=<redirect-uri-of-client-application>.
You are getting the username password popup because token endpoint is already protected through spring's BasicAuthenticationFilter and it is expecting you to pass your client_id as username and client_secret as password. Instead of token endpoint you need to protect authorization endpoint so do your endpoint security configuration as given...
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().antMatchers("/oauth/authorize").authenticated()
.and()
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.csrf().disable();
}