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 :)
Related
I'm trying to configure an authorization server with spring-security-oauth2 and jwt.
My main :
#SpringBootApplication
#EnableResourceServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My Security config :
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("john").password("123").roles("USER").authorities("FOO_READ");
}
}
My auth server config :
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gateway")
.secret("secret")
.authorizedGrantTypes("refresh_token", "password")
.scopes("GATEWAY")
.autoApprove(true) ;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenEnhancer(jwtTokenEnhancer()).authenticationManager(authenticationManager);
}
}
And a webservice to access the current user :
#RestController
public class UserController {
#GetMapping("/users/me")
public Principal user(Principal principal) {
return principal;
}
}
Now, if I do a post request like this :
gateway:secret#localhost:10101/oauth/token
grant_type : password
username : john
password : 123
I have a response like :
{
"access_token": "...access token...",
"token_type": "bearer",
"refresh_token": "...refresh token...",
"expires_in": 43199,
"scope": "GATEWAY",
"jti": "...jti..."
}
If I use the access token to call my user WS :
GET localhost:10101/users/me
Authorization : Bearer ...access token...
I obtain a null response.
But if I had this lines to my Security config :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/wtf");
}
I then obtain the appropriate response.
Can someone explain me how this is working ? Why my user is not recognized without this additional line ?
Thank you.
Actually it's a problem with spring security.
The solution is in this thread :
https://github.com/spring-projects/spring-security-oauth/issues/980
This annotation should be added for the Adapter to be correctly called :
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
// ...
}
Another solution is to implement ResourceServerConfigurerAdapter instead of WebSecurityConfigurerAdapter.
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 have a system using Spring Boot, Angular 2, Spring OAuth 2 where I have implemented security using #EnableWebSecurity and implemented oauth using #EnableResourceServer and #EnableAuthorizationServer in the same application.
Following are the implemented classes:
SecurityConfig.java
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("pass").roles("USER").and()
.withUser("username").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/private/**").hasRole("USER")
.antMatchers("/public/**").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT","USER")
.scopes("read", "write", "trust")
.secret("secret")
.accessTokenValiditySeconds(1200).
refreshTokenValiditySeconds(6000);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("hasAuthority('USER')");
}
}
ResourceServerConfig.java
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/public/**").permitAll();
http.authorizeRequests().antMatchers("/private/**").hasRole("USER");
}
}
All the url following the /public are accessible by any users; it's correct. The urls following /private/ are secured by both ResourceServerConfig and SecurityConfig, therefore it's not accessible to anonymous users.
When I request access_token from authorization server using grant_type=password, I get the access_token which I use to access the secured resources by appending the access_token as the parameter. But still the resources are not available and I get the response as below:
localhost:8080/private/user/test/?access_token=92f9d86f-83c4-4896-a203-e21976d4cfa2
{
"timestamp": 1495961323209,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/private/user/test/"
}
But when I remove the antMatchers from SecurityConfig.configure(HttpSecurity), the resources are no longer secured even if the ResourceServerConfig.configure(HttpSecurity) is securing the patters.
My questions:
Is there anything I need to perform in ResourceServerConfig so as to grant access to authorized users from the resource server?
What is difference between #EnableResourceServer and #EnableWebSecurity? Do I need to implement both in this application? (I couldn't find any good answers for this questions)
Your private resource is well secured, but the access_token obtained is not passed the correct way to the server.
You have to pass it as header of the request with
Authorization: Bearer 92f9d86f-83c4-4896-a203-e21976d4cfa2
or as curl command:
curl -H "Authorization: Bearer 92f9d86f-83c4-4896-a203-e21976d4cfa2"
I've an Oauth2 Server implemented with spring-security-oauth2, works well, authenticate the user, refresh token, and if i pass an authentication header "Bearer xxxx" to a rest endpoint in it, works well too.
The problem, I want to create a rest service that comunicate with the oauth server passing the header "Bearer xxxx" and aproves the connection, there are any kind of solution for that? i'm really desesperated...
The Service-Auth =>
AuthorizationServer:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
ResourceServer:
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/auth/**").authenticated();
}
}
Security Config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
public static Properties getProps() throws IOException {
Properties prop = new Properties();
try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("crowd.properties")) {
prop.load(in);
}
return prop;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/api/v1/secure/hello");
}
#Bean
public CrowdUserDetailsService crowdUserDetailsService() throws IOException {
CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl();
cusd.setAuthorityPrefix("ROLE_");
return cusd;
}
#Override
#Bean
public org.springframework.security.authentication.AuthenticationManager authenticationManagerBean() throws Exception {
return new AuthenticationBasic();
}
}
application.properties:
# Database
spring.datasource.url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/oauth2
spring.datasource.username=admin
spring.datasource.password=password
spring.datasource.schema=oauth2
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# Test connection every hour Database
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
spring.datasource.timeBetweenEvictionRunsMillis = 60000
# Web server
server.port=8080
server.contextPath=/api/v1/secure
The Service-Client =>
Application:
#SpringBootApplication
#EnableOAuth2Client
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
}
RestController:
#Controller
public class HelloController {
#RequestMapping(value = "/hello",
method = RequestMethod.GET)
#ResponseBody
public String sayHello() {
return "Hello User!";
}
}
application.yml:
security:
oauth2:
client:
clientId: client
clientSecret: secret
accessTokenUri: http://localhost:8080/api/v1/secure/oauth/token
userAuthorizationUri: http://localhost:8080/api/v1/secure/oauth/authorize
clientAuthenticationScheme: header
resource:
tokenInfoUri: http://localhost:8080/api/v1/secure/oauth/check_token
server:
port: 8082
contextPath: /api/v1/auth
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