Why does Spring Security not prevent POST request for USER role? [duplicate] - spring-security

This question already has answers here:
How to fix role in Spring Security?
(2 answers)
Closed 7 days ago.
A Spring UserDetailsService contains an admin with role ADMIN and an user with role USER:
#Bean
public UserDetailsService uds() {
UserDetails admin = User.builder()
.username("admin")
.password(encoder().encode("pwa"))
.roles("ADMIN")
.build();
UserDetails user = User.builder()
.username("user")
.password(encoder().encode("pwu"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
The repository extends CrudRepository
#RepositoryRestResource
public interface CountryRepo extends CrudRepository<Country, Long> { }
where POST requests map to the save method.
The SecurityFilterChain requires POST requests to be sent by clients with ADMIN role (see (*)):
#Bean
SecurityFilterChain configureSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authCustomizer -> authCustomizer
.requestMatchers("/api/countries").permitAll()
.requestMatchers("/h2").permitAll()
.requestMatchers(HttpMethod.POST, "/api/countries").hasRole("ADMIN") // <-- (*)
.anyRequest().authenticated()
)
.csrf().disable()
.httpBasic(withDefaults());
http.headers().frameOptions().disable();
return http.build();
}
csrf and frameOptions are needed to use the H2 console in the browser.
An admin can create countries with CURL:
C:\Users\project>curl -H "Content-Type: application/json" -u admin:pwa -X POST http://localhost:8080/api/countries -d "{\"countryCode\":\"AUS\"}"
{
"countryCode" : "AUS",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/countries/4"
},
"country" : {
"href" : "http://localhost:8080/api/countries/4"
}
}
}
However, so can a user! I expected to get a 401 or 403 HTTP status code here.
C:\Users\mahed>curl -H "Content-Type: application/json" -u user:pwu -X POST http://localhost:8080/api/countries -d "{\"countryCode\":\"CAN\"}"
{
"countryCode" : "CAN",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/countries/5"
},
"country" : {
"href" : "http://localhost:8080/api/countries/5"
}
}
}
Why are the POST requests by user, who does not have the ADMIN role, successful?

The requestMatcher matching the POST request needs to be before the general requestMatcher. This filter chain works:
#Bean
SecurityFilterChain configureSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authCustomizer -> authCustomizer
.requestMatchers(HttpMethod.POST, "/api/countries").hasRole("ADMIN")
.requestMatchers("/api/countries").permitAll()
.requestMatchers("/h2").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.httpBasic(withDefaults());
http.headers().frameOptions().disable();
return http.build();
}

Related

Which one is the correct OAuth2 flow

Tried to implement an OAuth2 in spring. But stuck as to which one would be the correct flow?
One flow I keep #Order(1) in (WebSecurityConfigurerAdapter)
Here on hitting the below I am presented with the default login page and I successfully login. http://localhost:8301/oauth/authorize?client_id=getidfromfacebook&response_type=code&redirect_uri=http://localhost:9191/xyz
Redirected to the authorize page and after acceptance get a code http://localhost:9191/xyz?code=mkuyG4 which helps in getting the access and refresh token by curl http://localhost:8301/oauth/token -H"Content-type: application/x-www-form-urlencoded" -d'grant_type=authorization_code&redirect_uri=http://localhost:9191/xyz&code=LJQef7' -u getidfromfacebook:getit
I am also able to get a fresh access token from the given refresh token via curl --location --request POST 'http://localhost:8301/oauth/token?grant_type=refresh_token&client_id=getidfromfacebook&refresh_token=a045acd6-5d66-4db5-a509-4bdadca065e0' -u getidfromfacebook:getit
The problem I face here is that with the given access token, I am not able to access any of the resources mentioned in
antMatchers("/api/**").authenticated() (ResourceServerConfigurerAdapter).
Like in postman provided a Header with Authorization and value Bearer access-token or like curl -H"Authorization: Bearer 1738520f-9f9c-43ef-8f7f-f5886075a7aa" http://localhost:8301/api/users/all/.
Note, I am able to get access-tokens for other grant_types as well and also refresh it. But no access to resources via the token. Point to note is if I hit the resource url, I am presented with the default login and able to access it.
The other flow I remove #Order(1). When I try to go through the authorization code flow, the system complains about user needs to be logged in for a request of (auth)code. So not able to proceed as am not presented with the default login page.
However, I am able to proceed with the password grant type curl http://localhost:8301/oauth/token -d"grant_type=password&username=username&password=userpassword" -H"Content-type:application/x-www-form-urlencoded; charset=utf-8" -u getidfromfacebook:getit
I am also able to access the resources via the access token.
Which one is the correct approach?
Why am I not able to access the resources in former approach.
#Configuration
#EnableAuthorizationServer
#AllArgsConstructor
public class AuthorizationServerConfigAdapter extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final ClientService clientService;
private final UserService userService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(userService)
;
}
/*****************************/
#Configuration
#EnableResourceServer
public class ResourceServerConfigAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}
}
/*****************************/
#Configuration
#EnableWebSecurity
#AllArgsConstructor
#Order(1) // Since we have this working as N, Z and R sever.
public class WebSecurityConfigAdapter extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
}
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Bearer"));
})
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}

Jhipster OAuth 2.0 / OIDC Authentication Authorization header with bearer token

I’ve used Jhipster to generate an app with the security option OAuth 2.0 / OIDC Authentication. I reconfigured said app to use Okta instead of keycloak following the instructions at http://www.jhipster.tech/security/#okta. All works as expected and the login flow performs as expected.
I now want to use OAuth 2.0 access_tokens to access my api resources from additional clients (Postman, Wordpress). I’ve retrieved a valid token from Okta added it to my Postman get request for localhost:8080/api/events and get a 401 in response.
The logs (https://pastebin.com/raw/R3D0GHHX) show that the spring security oauth2 doesn’t seem to be triggered by the presence of the Authorization bearer token.
Does Jhipster with OAuth 2.0 / OIDC Authentication support
access_token in the Authorization bearer header or url param out of
the box?
If not can you suggest what additional configurations I should make?
OAuth2Configuration.java
#Configuration
#Profile("dev")
public class OAuth2Configuration {
public static final String SAVED_LOGIN_ORIGIN_URI = OAuth2Configuration.class.getName() + "_SAVED_ORIGIN";
private final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
#Bean
public FilterRegistrationBean saveLoginOriginFilter() {
Filter filter = new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
if (request.getRemoteUser() == null && request.getRequestURI().endsWith("/login")) {
String referrer = request.getHeader("referer");
if (!StringUtils.isBlank(referrer) &&
request.getSession().getAttribute(SAVED_LOGIN_ORIGIN_URI) == null) {
log.debug("Saving login origin URI: {}", referrer);
request.getSession().setAttribute(SAVED_LOGIN_ORIGIN_URI, referrer);
}
}
filterChain.doFilter(request, response);
}
};
FilterRegistrationBean bean = new FilterRegistrationBean(filter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public static DefaultRolesPrefixPostProcessor defaultRolesPrefixPostProcessor() {
return new DefaultRolesPrefixPostProcessor();
}
public static class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FilterChainProxy) {
FilterChainProxy chains = (FilterChainProxy) bean;
for (SecurityFilterChain chain : chains.getFilterChains()) {
for (Filter filter : chain.getFilters()) {
if (filter instanceof OAuth2ClientAuthenticationProcessingFilter) {
OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter =
(OAuth2ClientAuthenticationProcessingFilter) filter;
oAuth2ClientAuthenticationProcessingFilter
.setAuthenticationSuccessHandler(new OAuth2AuthenticationSuccessHandler());
}
}
}
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
}
SecurityConfiguration.java
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}
application.yml
security:
basic:
enabled: false
oauth2:
client:
access-token-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/token
user-authorization-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/authorize
client-id: <okta-client-id>
client-secret: <okta-client-secret>
client-authentication-scheme: form
scope: openid profile email
resource:
filter-order: 3
user-info-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/userinfo
token-info-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/introspect
prefer-token-info: false
server:
session:
cookie:
http-only: true
Matt's answer point me to the right direction, thanks!
and here is my current working configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OAuth2AuthenticationConfiguration extends ResourceServerConfigurerAdapter {
#Bean
public RequestMatcher resources() {
return new RequestHeaderRequestMatcher("Authorization");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(resources())
.authorizeRequests()
.anyRequest().authenticated();
}
}
This answer was helpful too, thanks.
You need to use Spring Security OAuth's #EnableResourceServer for this functionality. If you're using Okta, you can also try using its Spring Boot Starter.

Spring OAuth2 autorization from database

Hello everyone,
I'm practicing OAuth2 in Spring boot.I have developed the application when i access get resources, i'm getting the response but for post resources i have to provide username and password which i'm passing in request but it still gives me this response
curl -i --user admin:admin -H Accept:application/json -X PUT http://localhost:8080/api/user/addUpdateUser -H Content-Type: application/json -d '{ "userId": 3, "firstName": "M.Danish", "lastName": "Khan", "userName": "danishkhan", "address": "Mardan", "phone": "04543545435" }'
{
"timestamp": 1464778621656,
"status": 401,
"error": "Unauthorized",
"message": "Access Denied",
"path": "/api/user/addUpdateUser"
}
This my code.
Web Security Configuration
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().csrf().disable();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OAuth Resource Server Configuration
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private final String RESOURCE_ID="SpringOAuth";
#Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
/*#Autowired
private UserDetailsService userDetailsService;*/
#Override
public void configure(HttpSecurity http) throws Exception {
http .exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest().authenticated()
/*.and().userDetailsService(userDetailsService); was just checking whether it will work with this or not*/
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
}
OAuth Authorization Server Configuration
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final String RESOURCE_ID="SpringOAuth";
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
private UserDetailsService userDetailsService;
#Autowired
AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.authorizedGrantTypes("password", "refresh_token")
.authorities("ROLE_USER")
.scopes("read")
.resourceIds(RESOURCE_ID)
.secret("secret").accessTokenValiditySeconds(3600);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
Controller
#Controller
#RequestMapping("/api/user")
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/addUpdateUser",method = RequestMethod.POST)
public ResponseEntity<Void> add_UpdateUser(#RequestBody User user){
if(user==null){
return new ResponseEntity<Void>(HttpStatus.EXPECTATION_FAILED);
}else{
userService.add_UpdateUser(user);
return new ResponseEntity<Void>(HttpStatus.CREATED);
}
}
#RequestMapping("/getAllUser")
public ResponseEntity<List<User>> getAllUsers(){
return new ResponseEntity<List<User>>(userService.getAllUsers(),HttpStatus.OK);
}
#RequestMapping(value = "/deleteUser",method = RequestMethod.POST)
public ResponseEntity<Void> deleteUser(#RequestBody String userName){
if(userName.equals("")){
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
}else {
userService.deleteUser(userName);
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
}
Your content type header must be surround by quotation marks because you have a space in it.
-H Content-Type: application/json
Should be
-H "Content-Type: application/json"
Otherwise the shell considers them as separate arguments. Like this
$ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer 27f9e2b7-4441-4c03-acdb-7e7dc358f783" -d '{"apiKey": "key", "tag": "tag"}' localhost:8080/isTagAvailable
Also you didn't get an access token first.

spring security mapping for wildcards

Using Spring-Boot 1.1.17, Spring-MVC with Spring-Security:
I have several subdomains that I want to to allow unauthenticated users (Visitors) access to. For example:
mysite.com/customerA
mysite.com/customerB
If a invalid customer site is attempted, then my controller would either throw an exception or redirect back to / (mysite.com/) Naturally other parts of the domain (mysite.com/customerA/myaccount) will require login.
I haven't really figured out how to do this with spring security and spring-mvc. Here is what I am attempting so far:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers( "/**/" ).permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/wizard").permitAll()
.antMatchers("/menu").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/libs/**").permitAll();
http
.formLogin()
.loginPage("/loginPage")
.permitAll()
.loginProcessingUrl("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/?tab=success")
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll()
.and()
.csrf();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/fonts/**","/libs/**");
}
}
And my homepage controller:
#Controller
#RequestMapping("/{officeName}/")
public class HomeController {
private AuthenticatedUser getVisitor(#PathVariable String officeName) {
.. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
When I try to access that url, I get the following errors:
o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/customerA/]
s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /customerA/
s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/customerA/]
o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/customerA/] are [/**]
o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/customerA/] are {}
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/customerA/] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2f295527] and 1 interceptor
o.s.web.servlet.DispatcherServlet : Last-Modified value for [/customerA/] is: -1
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: ServletContext resource [/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [META-INF/resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [static/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [public/]
o.s.w.s.r.ResourceHttpRequestHandler : No matching resource found - returning 404
I tried adding this ServletRegistrationBean:
#Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet );
registration.addUrlMappings("/", "/testCustomer/*" );
for ( Office office : officeService.findAllActiveOffices() ) {
registration.addUrlMappings( office.getUrl() + "/*" );
}
return registration;
}
But this would seem to only work if the application knows of the customer at startup, not dynamically in the case of customer signup.
Is there a way to configure this to handle these types of wildcards?
You can try with a configuration like the following:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService _userService;
#Autowired
private PasswordEncoder _passwordEncoder;
/**
* Defines the password encoder used by Spring security during the
* authentication procedure.
*/
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
/**
* Sets security configurations for the authentication manager
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(_userService)
.passwordEncoder(_passwordEncoder);
return;
}
/**
* Configures where Spring Security will be disabled (security = none).
* From spring reference: "Typically the requests that are registered [here]
* should be that of only static resources. For requests that are dynamic,
* consider mapping the request to allow all users instead."
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/css/**",
"/js/**",
"/fonts/**",
"/resources/**",
"/libs/**");
return;
}
/**
* Sets security configurations in the HttpSecurity object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set security configurations
http
.authorizeRequests()
// the following urls are allowed for any user (no authentication)
.antMatchers(
"/",
"/login",
"/menu")
.permitAll()
// any other url must be authenticated
.anyRequest().authenticated()
.and()
// define the login page url
.formLogin()
.loginPage("/login")
.permitAll()
.and()
// define the logout url
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
return;
} // method configure
} // class WebSecurityConfig
Adding your personal configurations... You can try to add the following controller:
#Controller
public class HomeController {
#RequestMapping("/{officeName}/")
public AuthenticatedUser getVisitor(#PathVariable String officeName) {
// .. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
}
If the user is correctly authenticated he should access the url at the officeName.

Spring Security OAuth2 Java Config for Google Login

I am migrating working XML configuration to Java configuration for Spring Security OAuth2 and using Google as the OAuth provider.
This is how my java configuration looks:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final List<String> scope;
static {
// Permissions to access email and profile
scope = new ArrayList<>(3);
scope.add("openid");
scope.add("email");
scope.add("profile");
}
#Autowired(required = true)
private UserService userService;
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
authorizeRequests()
.antMatchers(HttpMethod.GET, "/","/public/**", "/resources/**","/resources/public/**").permitAll()
//.antMatchers("/google_oauth2_login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/home")
.and()
.csrf().disable()
.logout()
.logoutSuccessUrl("/")
.logoutUrl("/logout")
.and()
.requiresChannel().anyRequest().requiresSecure()
.and()
.addFilterAfter(oAuth2ClientContextFilter(),ExceptionTranslationFilter.class)
.addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
.userDetailsService(userService);
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
// #formatter:off
auth
.authenticationProvider(googleOauth2AuthProvider())
.userDetailsService(userService);
// #formatter:on
}
#Bean
public GoogleOAuth2Filter googleOAuth2Filter() throws Exception {
GoogleOAuth2Filter filter = new GoogleOAuth2Filter(
"/google_oauth2_login",
"https://accounts.google.com/o/oauth2/auth",
oAuth2RestTemplate(auth2ProtectedResourceDetails()));
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
#Bean
public GoogleOauth2AuthProvider googleOauth2AuthProvider() {
GoogleOauth2AuthProvider authProvider = new GoogleOauth2AuthProvider();
return authProvider;
}
#Bean
public OAuth2ProtectedResourceDetails auth2ProtectedResourceDetails() {
AuthorizationCodeResourceDetails auth2ProtectedResourceDetails = new AuthorizationCodeResourceDetails();
auth2ProtectedResourceDetails
.setClientAuthenticationScheme(AuthenticationScheme.form);
auth2ProtectedResourceDetails
.setAuthenticationScheme(AuthenticationScheme.form);
auth2ProtectedResourceDetails.setGrantType("authorization_code");
auth2ProtectedResourceDetails
.setClientId("the-client-id");
auth2ProtectedResourceDetails
.setClientSecret("the-client-secret");
auth2ProtectedResourceDetails
.setAccessTokenUri("https://accounts.google.com/o/oauth2/token");
auth2ProtectedResourceDetails.setScope(scope);
auth2ProtectedResourceDetails
.setUserAuthorizationUri("https://accounts.google.com/o/oauth2/auth");
auth2ProtectedResourceDetails.setUseCurrentUri(false);
auth2ProtectedResourceDetails
.setPreEstablishedRedirectUri("https://localhost/google_oauth2_login");
return auth2ProtectedResourceDetails;
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(
OAuth2ProtectedResourceDetails resource) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource);
return oAuth2RestTemplate;
}
#Bean
public OAuth2ClientContextFilter oAuth2ClientContextFilter() {
OAuth2ClientContextFilter oAuth2ClientContextFilter = new OAuth2ClientContextFilter();
return oAuth2ClientContextFilter;
}
}
Note that I have disabled CSRF.
From my login page the user do gets redirected to Google login page
Problem:-
Google Permission Page just asks for "Have offline access".'Email/Profile' access request is missing.
The equivalent 'scope' attibute XML configuration :-
<oauth2:resource id="googleOauth2Resource" type="authorization_code"
client-id="the-client-id
client-secret="the-client-secret"
user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
scope="openid email profile" use-current-uri="false"
client-authentication-scheme="form" pre-established-redirect-uri="https://localhost/google_oauth2_login" />
do correctly asks for email and profile permissions. Why?
Continuing anyway with the 'Have offline access' results in this exception:-
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:243)
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:592)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:550)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:514)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAuthorizationCode(AuthorizationCodeAccessTokenProvider.java:145)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:196)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)
while trying to get user profile using this code block:
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
logger.info("Google Oauth Filter Triggered!!");
SecurityContext context = SecurityContextHolder.getContext();
// auth null or not authenticated.
String code = request.getParameter("code");
Authentication dummyAuthentication = null;
if (StringUtils.isEmpty(code)) {
// Google authentication in progress. will return null.
logger.debug("Will set dummy user in context ");
dummyAuthentication = getDummyAuthenticationToken();
context.setAuthentication(dummyAuthentication);
// trigger google oauth2.
oauth2RestTemplate.postForEntity(authURI, null, Object.class);
return null;
} else {
// response from google received !!.
// remove dummy authentication from context.
//SecurityContextHolder.clearContext();
logger.debug("Response from Google Recieved !!");
// get user profile and prepare the authentication token object.
ResponseEntity<Object> forEntity = oauth2RestTemplate.getForEntity(
HTTPS_WWW_GOOGLEAPIS_COM_PLUS_V1_PEOPLE_ME_OPEN_ID_CONNECT,
Object.class);
#SuppressWarnings("unchecked")
Map<String, String> profile = (Map<String, String>) forEntity
.getBody();
CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(
profile.get(EMAIL), profile.get(NAME));
authenticationToken.setAuthenticated(false);
return getAuthenticationManager().authenticate(authenticationToken);
}
}
Spring RestTemplate showing this in logs:
o.s.web.client.RestTemplate : POST request for "https://accounts.google.com/o/oauth2/auth" resulted in 400 (Bad Request); invoking error handler
2014-09-05 21:51:46.870 WARN 5836 --- [ qtp25546756-15] o.eclipse.jetty.servlet.ServletHandler : /google_oauth2_login
This same piece of code works while using with XML configuration.
UPDATE 1
I was able to fix the 'Offline Access' problem by changing scope to 'https://www.googleapis.com/auth/plus.profile.emails.read' & 'https://www.googleapis.com/auth/plus.login'.
Still getting bad request error while trying to get user profile
Please find source code for the problem here -
git clone https://kumarsambhavjain#bitbucket.org/kumarsambhavjain/spring-oauth2-login.git
Have you tried change profile URL to
https://www.googleapis.com/plus/v1/people/me/openIdConnect
See more: https://developers.google.com/+/api/openidconnect/getOpenIdConnect
I used your code to create a OAuth2 Spring Boot sample, quite similar, and I had same issue once I replaced profile URL to:
https://www.googleapis.com/plus/v1/people/me/openIdConnect
I resolved it by enabling Google+ API in Google Console:
Visit the Google API Console here:https://code.google.com/apis/console/?api=plus
Under the Services panel, make sure the Google+ API is turned "on".
In the APIs console, click API Access in the left menu.
Copy the API key presented towards the bottom. Include this API key in your HTTP request.
This process is explained in this question:
How to call https://www.googleapis.com/plus/v1/people/me at google

Resources