Springboot Web Security Configuration not redirecting to login page - spring-boot

I created a small application to learn sprint boot web security. The sample application is mostly inspired from partial instructions provided in "Spring Boot in Action" book.
The problem I am facing is that inspite of configuring access like below, when I open http://localhost:8080/readers/ishwar the application doesn't navigate to login page. It straightaway opens the page represented by http://localhost:8080/readers/ishwar. As far as I have understood the spring security if .access("") is applied then in order to check access by default spring should redirect the user to login page. But it is not.
package com.example.readingList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ReaderRepository readerRepository;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.
authorizeRequests()
.antMatchers("/readers").access("hasRole('READER')")
.antMatchers("/login").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureUrl("/login?error=true");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return readerRepository.getOne(username);
}
});
}
}
BUT if I change the code to below then on browsing http://localhost:8080/readers/ishwar I get redirected to login page. What is wrong in first construct? I mean why .access("") is not working
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ReaderRepository readerRepository;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.
authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureUrl("/login?error=true");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return readerRepository.getOne(username);
}
});
}
}
Below is how ReaderRepository written
package com.example.readingList;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReaderRepository extends JpaRepository<Reader, String> {
}
Below is Reader Class. I have deliberately granted "WRITER" role as I want to see that attempt to access page should fail as READER permission is missing. But as such even if I include "READER" permission there is no change in behaviour.
package com.example.readingList;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Arrays;
import java.util.Collection;
#Entity
public class Reader implements UserDetails {
private static final long serialVersionUID = 1L;
#Id
private String username;
private String password;
private String fullname;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("WRITER"));
}
//#Override
public String getPassword() {
return password;
}
public void setPassword(String password)
{
this.password = password;
}
//#Override
public String getUsername() {
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getFullname()
{
return fullname;
}
public void setFullname(String fullname)
{
this.fullname = fullname;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Kindly note that I haven't implemented the data source to store/fetch user and password. I was expecting to see at least the redirection to http://localhost:8080/login first but not happening if I use first construct.
Below is my pom.xml file
https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<groupId>com.example</groupId>
<artifactId>readingList</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>readingList</name>
<description>Reading List project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The ReadingListApplication class
#Configuration
#SpringBootApplication
public class ReadingListApplication implements WebMvcConfigurer
{
public static void main(String[] args) {
SpringApplication.run(ReadingListApplication.class, args);
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
ReadingListController class
package com.example.readingList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
#Controller
public class ReadingListController {
private ReadingListRepository readingListRepository;
#Autowired
public ReadingListController(ReadingListRepository readingListRepository)
{
this.readingListRepository = readingListRepository;
}
#RequestMapping(value="/readers/{reader}", method= RequestMethod.GET)
public String readersBook(#PathVariable("reader") String reader, Model model)
{
List<Book> readingList = readingListRepository.findByReader(reader);
if (readingList.isEmpty() == false) {
model.addAttribute("books", readingList);
}
return "readingList";
}
#RequestMapping(value="/readers/{reader}", method= RequestMethod.POST)
public String addToReadingList(#PathVariable("reader") String reader, Book book)
{
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/readers/{reader}";
}
}

It has to do with the AntPathRequestMatcher. In the first example, calling .antMatchers("/readers").access("hasRole('READER')") will not match /readers/ishwar.
Of course authorizeRequests().anyRequest().authenticated() will, which is why you were getting different behavior.
Try the following:
http.antMatchers("/readers/**")
.access("hasRole('READER')")
...

Related

Spring Boot 2.5.3 OAuth2 - Auth-Server and Webservice separate, Login error

Following the example on https://developer.okta.com/blog/2019/03/12/oauth2-spring-security-guide using the projects Create an OAuth 2.0 Server and Build Your Client App I cannot get it running without error.
I don't use Thymeleaf, as my Webservice returns data, not a page.
OAuth 2.0 Server project
#SpringBootApplication
#EnableResourceServer
public class Demo2Application {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
public AuthorizationServerConfig(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("abcd")
.secret(passwordEncoder.encode("fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9"))
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true)
.redirectUris("http://localhost:8082/login/oauth2/code/");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");;
}
}
#Configuration
#Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("doe"))
.roles("USER");
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#RestController
public class UserController {
#GetMapping("/user/me")
public Principal user(Principal principal) {
return principal;
}
}
application.properties
server.port=8090
pom.xml
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
I omit the context path, which the project originally uses.
Webservice project
#RestController
public class MyRESTController {
#GetMapping("/securedPage")
public String securedPage(Principal principal) {
return "securedPage";
}
#GetMapping("/")
public String index(Principal principal) {
return "index";
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
}
application.properties
server.port=8082
server.servlet.session.cookie.name=UISESSION
spring.security.oauth2.client.registration.custom-client.client-id=abcd
spring.security.oauth2.client.registration.custom-client.client-secret=fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9
spring.security.oauth2.client.registration.custom-client.client-name=Auth Server
spring.security.oauth2.client.registration.custom-client.provider=custom-provider
spring.security.oauth2.client.registration.custom-client.scope=user_info
spring.security.oauth2.client.registration.custom-client.redirect-uri=http://localhost:8082/login/oauth2/code/
spring.security.oauth2.client.registration.custom-client.client-authentication-method=basic
spring.security.oauth2.client.registration.custom-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.custom-provider.authorization-uri=http://localhost:8090/oauth/authorize
spring.security.oauth2.client.provider.custom-provider.token-uri=http://localhost:8090/oauth/token
spring.security.oauth2.client.provider.custom-provider.user-info-uri=http://localhost:8090/user/me
spring.security.oauth2.client.provider.custom-provider.user-name-attribute=name
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
When running both porojects, with localhost:8082 in the web browser I get index as response.
With localhost:8082/securedPage I get redirected to the login page, entering username john and password doe I get the following error page:
Login with OAuth 2.0
[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource: 404 : [{"timestamp":"2021-07-30T07:58:54.529+00:00","status":404,"error":"Not Found","path":"/user/me"}]
Auth Server
Don't know what the error is causing. Looks like it is related to the Webservice application property and the URL to the UserController in the OAuth 2.0 Server project
spring.security.oauth2.client.provider.custom-provider.user-info-uri=http://localhost:8090/user/me
One additional question is:
Can the login form be avoided? The credentials passed somehow.
If the client is not the web browser but some other application which has no UI. Or perhaps curl. I also have MockMvc tests, so far it was Basic Auth, now OAuth2, how will be those affected?

Swagger UI version 3 returning a 404 page but api-docs is working

I am trying to setup swagger-UI to test my springboot REST API rather than using postman however after going through a few tutorials and some questions i can not seem to get past the 404 error when trying to access the html page via my browser.
My dependencies :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
SpringConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
and my controller
#Controller
#CrossOrigin
#RequestMapping(path = "/api")
public class AppController {
#ResponseBody
#PostMapping(path ="/api")
public String home() {
return "Hello World";
}
#GetMapping(path = { "/api/Teacher/{id}"})
public TeacherPayload getTeacher(#PathVariable(required=false,name="id") String id) throws
Exception{
if (id != null) {
return teacher.getTeacher(id);
} else {
return teacher.getTeachers();
}
....
I changed my port number fro the default 8080 to 3005 but i do not think that should be the problem as i tried reverting back to 8080 to no avail.
Edit: My security config is as follows, note i permitted all paths to bypass the security whilst testing
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsServiceImpl jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/authenticate");
web.ignoring().antMatchers("/recoverPortal");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.cors();
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "**").permitAll()
.antMatchers("/**").permitAll()
.antMatchers("/newUser").authenticated()
.antMatchers("/newUser").authenticated()
.antMatchers("/admin/**").hasAnyAuthority("USER_ADMIN")
.antMatchers("/resetPassword").authenticated()// all other requests need to be authenticated
.anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
What endpoint are you hitting? It should be /swagger-ui/ at the end, not /swagger-ui.html. Also, I think you can omit the springfox-swagger-ui dependency. All I need in my setup is the springfox-boot-starter one.

Spring boot security oauth2 authorization_code flow login page not found

Hi i am trying to implement OAuth2 using spring security, so far i have managed to implement it using grant_type=password i am getting token, time, refresh token and resources are guarded as expected.
Now my next step is to use authorization_code+pkce implementation.
I have followed this tutorial, when i replace all the client credentials with my auth server details i get http://localhost:8080/oauth/login Not Found message. The request goes something like this http://localhost:8080/auth/oauth/authorize?client_id=SampleClientId&redirect_uri=http://localhost:8083/ui2/login&response_type=code&state=5ppnu6
My Configuration and server files are as follow, this is my first time implemeting oauth on server side so i might be doing very non-sense, please help me correct that.
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private AuthenticationManager manager;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("SampleClientId")
.secret(passwordEncoder.encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "implicit", "refresh_token")
.scopes(UserDetailsServiceImpl.Role.USER.name(),
UserDetailsServiceImpl.Role.MODERATOR.name(),
UserDetailsServiceImpl.Role.ADMIN.name())
.redirectUris("http://localhost:8080/callback", "http://localhost:8083/ui2/login")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(4800);
}
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(manager);
}
}
BeansConfig.java
#Configuration
public class BeansConfig {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return new UserDetailsServiceImpl();
}
}
ResourceServerConfig.java
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.authorizeRequests()
.antMatchers("/users/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userDetailsService")
private UserDetailsServiceImpl userDetailsService;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
public SecurityConfig() {
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
userDetailsService.init(passwordEncoder);
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
UsersControllers.java
#RestController
public class UsersControllers {
#RequestMapping(value = "/users", method = RequestMethod.GET)
ResponseEntity<Map<String, String>> get() {
final Map<String, String> map = new HashMap<>();
map.put("status", "ok");
return ResponseEntity.ok(map);
}
#GetMapping(value = "/user/me")
Principal me(Principal principal) {
final Map<String, String> map = new HashMap<>();
map.put("status", "ok");
return principal;
}
}
UserDetailsImpl.java
public class UserDetailsImpl implements UserDetails {
private final String username;
private final String password;
private final List<GrantedAuthority> roles;
public UserDetailsImpl(String username, String password, List<GrantedAuthority> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return this.username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
UserDetailsServiceImpl.java
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger LOGGER = Logger.getLogger(UserDetailsServiceImpl.class.getSimpleName());
public enum Role {
USER,
MODERATOR,
ADMIN
}
private final List<UserDetailsImpl> users = new ArrayList<>();
public UserDetailsServiceImpl() {
}
public void init(BCryptPasswordEncoder passwordEncoder) {
users.add(new UserDetailsImpl("john", passwordEncoder.encode("doe"), buildUserAuthorities()));
users.add(new UserDetailsImpl("wow", passwordEncoder.encode("baby"), buildModeratorAuthorities()));
}
private List<GrantedAuthority> buildUserAuthorities() {
final List<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority(Role.USER.name()));
return authorityList;
}
private List<GrantedAuthority> buildModeratorAuthorities() {
final List<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority(Role.MODERATOR.name()));
return authorityList;
}
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
for (UserDetailsImpl details : users) {
if (details.getUsername().equals(s)) {
LOGGER.warning("Found user: " + s);
return details;
}
}
throw new UsernameNotFoundException("User " + s + " notfound");
}
public List<UserDetailsImpl> getUsers() {
return users;
}
}
Some of the contents from maven file
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

Getting 403 Forbidden when trying to get authorization code using the authorization code grant type

I have resource, authorization written using Spring boot and OAuth2. The resources are going to access by another web server application. So I thought of using the authorization code grant type but I also want to skip the approval screen.
I sent the following url to the auth server to get the authorization code from postman along with the basic authorization in the headers.
http://localhost:8081/OAuth2ExampleApplication/oauth/authorize?response_type=code&client_id=my-trusted-client&redirect_uri=http://localhost:8080/clientapp/auth/callback&scope=read%20write
But I am getting the 403 Forbidden Access Denied from the auth server.
Here is my complete code.
Resource Server:
ResourceServerConfiguration.java
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "my_rest_api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.requestMatchers().antMatchers("/user/**")
.and().authorizeRequests()
.antMatchers("/user/**").access("hasRole('ADMIN')")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Authorization Server :
AuthorizationServerConfiguration.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private AuthorizationCodeServices authorizationCodeServices;
#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")
.scopes("read", "write", "trust")
.secret("secret")
.redirectUris("http://localhost:8080/clientapp/auth/callback")
.autoApprove(true)
.accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.
refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager).authorizationCodeServices(authorizationCodeServices);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
Since I set autoApprove, I think the auth server will skip the login screen and send the authorization code directly to the redirectUrl (auth client app).
WebSecurityConfiguration:
OAuth2SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("bill").password("abc123").roles("ADMIN").and()
.withUser("bob").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/authorize", "/oauth/token").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pg.oauth2.example</groupId>
<artifactId>oauth2-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>oauth2-example</name>
<description>OAuth2 Example</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

spring boot oauth2 configuration: resource server remains unprotected

I have implemented authorization server and resource server using spring boot. authorization server works fine and I am able to get tokens. But my resource server remains unprotected. My objective is that resource server ahould only be accessed by someone having valid access token.
My entire code is:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
TokenStore tokenStore;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("client")
.scopes("read", "write")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.authorizedGrantTypes("password", "refresh_token")
.secret("secret")
.accessTokenValiditySeconds(180)
.refreshTokenValiditySeconds(600);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security); //To change body of generated methods, choose Tools | Templates.
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenServices(tokenServices())
.resourceId("MY_RESOURCE");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests()
.antMatchers("/").access("hasRole('USER')")
.antMatchers("/secure/").access("hasRole('ADMIN')")
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
return defaultTokenServices;
}
}
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("bill").password("abc123").roles("ADMIN").and()
.withUser("bob").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll();
}
}
#Configuration
#EnableGlobalMethodSecurity
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
#SpringBootApplication
#RestController
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
#GetMapping(value = "/")
public ResponseEntity<?> hello(){
return ResponseEntity.ok("Hello World");
}
#GetMapping(value = "/secure/")
public ResponseEntity<?> secure(){
return ResponseEntity.ok("Secure Resorce");
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>boot-oauth2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>boot-oauth2</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
What am I missing?
Thanks for help.
UPDATE:
I figured out that my resource server is unprotected because of presence of OAuth2SecurityConfig class. If I remove this class and add following class (where I have moved the inMemmory users), then resource server is protected as required
#Configuration
public class WebSecurityGlobalConfig extends GlobalAuthenticationConfigurerAdapter {
#Autowired
UserService userService;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("bill").password("abc123").roles("ADMIN").and()
.withUser("bob").password("abc123").roles("USER");
}
}
So, I am sensing improper HttpSecurity configuration in OAuth2SecurityConfig class is conflicting with resource server config.
So, How can I configure HttpSecurity of OAuth2SecurityConfig so, that it does allow access token protection for resource server paths and normal web security for non-resource server paths
finally after a lot of googling, I found the solution.
It was due to order of filters. order of OAuth2 resource filter has been changed in spring-boot-1.5.1. as change log says
The default order of the OAuth2 resource filter has changed from 3 to
SecurityProperties.ACCESS_OVERRIDE_ORDER - 1. This places it after the
actuator endpoints but before the basic authentication filter chain.
The default can be restored by setting
security.oauth2.resource.filter-order = 3
So, I changed the order of my OAuth2 resource server filter to 3 by setting it in application.properties security.oauth2.resource.filter-order = 3 and my problem was solved.
Annotate your OAuth2SecurityConfig with #EnableGlobalMethodSecurity(prePostEnabled = true)
I had the same issue.
I had another class extending WebSecurityConfigurerAdapter that i guess was in conflict with AuthorizationServerConfigurerAdapter.
I just removed the WebSecurityConfigurerAdapter class and it worked.

Resources