receiving null when trying to get connected user from Principal interface - spring

I created some apis and I protected them using jwt, authntication part is working well and I can validate the token when I get it from the header, now I want to add some role based rules, to get them I need to pass the email information from the token to another api.
The problem is that when I try to get information from the token using Principal interface I got a null response, here is the security config :
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${security.allowed-origin:*}")
private String allowedOriginPattern;
#Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwtSetUri;
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwtSetUri).build();
return jwtDecoder;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authRequests - > {
// Permissions on your endpoints
authRequests.mvcMatchers(HttpMethod.OPTIONS).permitAll();
// Management endpoints
authRequests.mvcMatchers("/management/**", "/docs/**", "/webjars/**").permitAll();
// else
authRequests.anyRequest().authenticated();
});
http.oauth2ResourceServer().jwt();
http.cors()
.and()
.cors().and()
.csrf().disable()
.exceptionHandling()
.accessDeniedHandler(securityAccessDeniedHandler())
.authenticationEntryPoint(securityAuthEntryPoint());
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(List.of("*"));
corsConfiguration.setAllowedMethods(List.of("*"));
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Bean
public AccessDeniedHandler securityAccessDeniedHandler() {
return new SecurityAccessDeniedHandler();
}
#Bean
public AuthenticationEntryPoint securityAuthEntryPoint() {
return new SecurityAuthEntryPoint();
}
}
and here is the api :
#GetMapping("/user")
public String getConnectedUser(Principal principal) {
// principal is null here
return principal.getName();
}

Related

include ROLE_ADMIN by default in #RolesAllowed annotation

I created a role based application, I know that the admin has all the rights so instead of adding ROLE_ADMIN evry time, I am locking for a way to give the admin all the rights, so instead of writting #RolesAllowed({"ROLE_ADMIN","ROLE_MANAGER","ROLE_CLIENT"}), I want only to write #RolesAllowed({"ROLE_MANAGER","ROLE_CLIENT"}), beucause I have to many APIs, and including ROLE_ADMIN each time is not a good deal, here is my security config :
public SecurityConfig(InnerRbacProxy rbacProxy) {
this.rbacProxy = rbacProxy;
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(jwtSetUri).build();
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.cors()
.and()
.cors().and()
.csrf().disable() //NOSONAR
.exceptionHandling()
.accessDeniedHandler(securityAccessDeniedHandler())
.authenticationEntryPoint(securityAuthEntryPoint());
if (!securityEnabled) {
http.authorizeRequests().anyRequest().permitAll();
} else {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authRequests -> {
// Permissions OPTIONS
authRequests.mvcMatchers(HttpMethod.OPTIONS).permitAll();
// Management endpoints
authRequests.mvcMatchers("/management/**", "/docs/**", "/webjars/**").permitAll();
// Business APIs
authRequests.anyRequest().authenticated();
});
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
}
// #formatter:on
}
#Bean
PasswordEncoder setPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(List.of("*"));
corsConfiguration.setAllowedMethods(List.of("*"));
corsConfiguration.setAllowedOriginPatterns(List.of("*"));
var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Bean
public AccessDeniedHandler securityAccessDeniedHandler() {
return new SecurityAccessDeniedHandler();
}
#Bean
public AuthenticationEntryPoint securityAuthEntryPoint() {
return new SecurityAuthEntryPoint();
}
JwtAuthenticationConverter jwtAuthenticationConverter() {
var customAuthoritiesConverter = new CustomAuthoritiesConverter();
var authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(customAuthoritiesConverter);
authenticationConverter.setPrincipalClaimName("email");
return authenticationConverter;
}
static class CustomAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
public CustomAuthoritiesConverter() {
}
#Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
var roleDto = rbacProxy.getRolesByEmail("user2#test.ma");
var roles = roleDto.getRoles().stream().map(RoleDto::getCode).collect(Collectors.toList());
List<GrantedAuthority> updatedAuthorities = new ArrayList<>();
roles.forEach(role -> updatedAuthorities.add(new SimpleGrantedAuthority(role)));
return updatedAuthorities;
}
}
}

Angular 6 Http Interceptors, request headers not modified

I created an interceptor to add an authorization header to each request sent by the client, here is the code :
import { HttpInterceptor, HttpRequest, HttpHandler, HttpHeaderResponse, HttpSentEvent, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpEvent, HttpHeaders } from "#angular/common/http";
import { Observable } from "rxjs";
import { Injectable } from "#angular/core";
#Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log(localStorage.getItem('jwtToken'));
if(localStorage.getItem('jwtToken')){
const request = req.clone({
setHeaders: {
Authorization: `bearer ${localStorage.getItem('jwtToken')}`
}
});
console.log(request.headers.get("Authorization"));
return next.handle(request);
}
return next.handle(req);
}
}
When a request is sent the function intercept is called and the authorization header is correclty set with the token value in the variable "request" as you can see there :
token console screenshot
But the authorization header doesn't appear in the request sent by my browser : network request headers and the backend cannot resolve the token.
Do you know why ?
Here is my spring config:
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public final static String AUTHORIZATION_HEADER = "Authorization";
#Autowired
UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(getProvider());
}
#Bean
public JwtTokenFilter jwtAuthenticationFilter() {
return new JwtTokenFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**")
.permitAll()
.anyRequest()
.authenticated();
}
#Bean
public AuthenticationProvider getProvider() {
AppAuthProvider provider = new AppAuthProvider();
provider.setUserDetailsService(userService);
return provider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
CorsConfig.java
#Configuration
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("*")
.allowedHeaders("*");
}
};
}
}
Your problem resides into backend services. For security reasons by default only some headers are accepted, the others are ignored.
To fix your problem you need to setup custom accepted headers. Authorization header, even if is like a standard for JWT, is considered a custom header.
I can give you an example of my Spring Security configuration:
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
Note the line
config.addAllowedHeader("*");
That means that my REST services accept all possible headers sent by the client.
Obviously it's not a good configuration, you should limit allowed headers and other things to match your needs, as restricted as is possible.
Obviously if you don't use Spring Security you need to find the way to do the same thing with yout language/framework.
This is my SecurityConfig.java It's a bit different from yours.
Try this and let me know
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private WLUserDetailsService userDetailsService;
#Value("${jwt.header}")
private String tokenHeader;
#Value("${jwt.route.authentication.path}")
private String authenticationPath;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoderBean());
}
#Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
// TODO adjust CORS management
.cors().and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// // disable page caching
// httpSecurity
// .headers()
// .frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
// .cacheControl();
}
#Override
public void configure(WebSecurity web) {
// AuthenticationTokenFilter will ignore the below paths
web
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
// allow anonymous resource requests
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
);
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
// config.addExposedHeader("Authorization, x-xsrf-token, Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, " +
// "Content-Type, Access-Control-Request-Method, Custom-Filter-Header");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

OAuth 2 with spring security and setting the State parameter in the redirect

I am using Spring boot with Spring security, with custom "Filter" Class calling to CIAM server with OAuth 2 authentication. I want to set explicitly or override the default setting so that I could set custom dynamic STATE parameter in the redirect URL that Spring Security prepares under the hood and sends the user to the CIAM server login page. This seamed trivial to me but it turned out to be far from that.
The goal is to add the custom STATE parameter of the OAuth2 redirect link so that after the authentication is finished and the CIAM server redirects me back to my page I take back the STATE parameter which is automatically included in the successful redirect link from the CIAM server.
The Security configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true,
proxyTargetClass = true)
#EnableOAuth2Client
#Order(3)
public class OAuth2LoginWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CiamOAuth2ClientFilter oAuth2CiamClientFilter;
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
return new InMemoryUserDetailsManager();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**/*.css", "/**/*.png", "/**/*.gif", "/**/*.jpg", "/h2-console/**", "/css/**",
"/img/**", "/font-awesome/**", "/fonts/**", "/js/**", "/signout","/signout/**", "/health");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/backoffice/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("API")
.antMatchers(/*"/", */"/login**", "/webjars/**", "/favicon.*", "/resources/**",
"/auth/**", "/signin/**","css/**","js/**", "/signup/**", "/signout/", "/health", "/awsTest/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/callback"))
.and()
.addFilterBefore(oAuth2CiamClientFilter.ciamFilter(), BasicAuthenticationFilter.class)
.logout()
.logoutUrl("/signout")
.logoutSuccessUrl("/logout");
}
}
The custom filter class
#Configuration
public class CiamOAuth2ClientFilter {
#Autowired
AuthorizationCodeResourceDetails oauth2CiamResourceDetails;
#Autowired
CiamOAuth2ClientProperties oauth2CiamClientProperties;
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Autowired
CiamPrincipalExtractor ciamPrincipalExtractor;
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
registration.addInitParameter("test", "trrrrrrr");
System.out.println("333333333333333333333333");
System.out.println(registration);
return registration;
}
public Filter ciamFilter() {
System.out.println("postaeget");
System.out.println(oauth2CiamClientProperties);
System.out.println(" _-------------------------------: " + oauth2CiamClientProperties.getResource().getUserInfoUri());
UserInfoTokenServices tokenService = new UserInfoTokenServices(oauth2CiamClientProperties.getResource().getUserInfoUri(), oauth2CiamResourceDetails.getClientId());
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oauth2CiamResourceDetails, oauth2ClientContext);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter("/login/callback");
tokenService.setRestTemplate(restTemplate);
tokenService.setPrincipalExtractor(ciamPrincipalExtractor);
filter.setRestTemplate(restTemplate);
filter.setTokenServices(tokenService);
return filter;
}
}
Application yml settings file connected with the issue
security:
oauth2:
client:
clientId: ...
clientSecret: ....
accessTokenUri: ...
userAuthorizationUri: ...
useCurrentUri: false
preEstablishedRedirectUri: https://localhost/login/callback
clientAuthenticationScheme: query
authenticationScheme: header
serverLogoutUrl: ..
postLogoutRedirectUri: https://localhost/signout
scope:
- openid
- profile
- email
- offline_access
state: TEST
resource:
userInfoUri: ...
preferTokenInfo: ...
In my case
I configure OAuth2ClientAuthenticationProcessingFilter somewhere in #Configuration:
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(API_LOGIN_FACEBOOK);
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProviderWithUrl = new AuthorizationCodeAccessTokenProvider();
authorizationCodeAccessTokenProviderWithUrl.setStateKeyGenerator(new StateKeyGeneratorWithRedirectUrl());
facebookTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProviderWithUrl);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new CheckedUserInfoTokenServices(
facebookResource().getUserInfoUri(), facebook().getClientId(),
facebookPrincipalExtractor, blogPreAuthenticationChecks(), blogPostAuthenticationChecks());
tokenServices.setAuthoritiesExtractor(new FacebookAuthoritiesExtractor());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
facebookFilter.setAuthenticationSuccessHandler(new OAuth2AuthenticationSuccessHandler());
return facebookFilter;
}
And you can access to current request in StateKeyGeneratorWithRedirectUrl with:
RequestContextHolder.getRequestAttributes()
so you can extract Referer header for example:
public class StateKeyGeneratorWithRedirectUrl extends DefaultStateKeyGenerator {
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
#Override
public String generateKey(OAuth2ProtectedResourceDetails resource) {
HttpServletRequest currentHttpRequest = getCurrentHttpRequest();
if (currentHttpRequest!=null){
String referer = currentHttpRequest.getHeader("Referer");
if (!StringUtils.isEmpty(referer)){
return generator.generate()+","+referer;
}
}
return generator.generate();
}
private static HttpServletRequest getCurrentHttpRequest(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
return ((ServletRequestAttributes)requestAttributes).getRequest();
}
return null;
}
}
Next - read state from callback:
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public static final String DEFAULT = "/";
#Override
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.query(request.getQueryString())
.build();
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
String stateEncoded = queryParams.getFirst("state");
if (stateEncoded == null) {
return DEFAULT;
}
String stateDecoded = URLDecoder.decode(stateEncoded, StandardCharsets.UTF_8);
String[] split = stateDecoded.split(",");
String redirect;
if (split.length != 2){
return DEFAULT;
} else {
return split[1];
}
}
}

Spring security OAuth parsing the response into an object

I'm new to spring.
I have been following http://spring.io/guides/tutorials/spring-boot-oauth2/ using google oauth in place of facebook.
I am able to return the principle as described in the tutorial, which sends to the browser the json returned by the api call and can use this client side.
#RestController
public class UserControllers {
#RequestMapping("/user")
public Principal user(Principal principal) {
return principal;
}
}
But how would I use this data on the server side? Suppose I wanted just to return the email address on the /user route? Or maybe I want to further populate the Principal with information from a database?
Below is the code I use to set up the oauth authentication.
#EnableWebSecurity
#EnableOAuth2Client
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
http .csrf().disable()
.logout().logoutSuccessUrl("/").permitAll() //logout logic handled by spring
.and()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**", "/user")
.permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
protected Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(constructFilter("/login/google", google(), googleResource()));
filter.setFilters(filters);
return filter;
}
private Filter constructFilter(String endpoint, OAuth2ProtectedResourceDetails clientDetails, ResourceServerProperties resourceDetails) {
OAuth2ClientAuthenticationProcessingFilter filter = new CustomOauth2AuthFilter(endpoint);
OAuth2RestTemplate template = new OAuth2RestTemplate(clientDetails, oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(resourceDetails.getUserInfoUri(), clientDetails.getClientId()));
return filter;
}
/*
/Returns a new AuthorizationCodeResourceDetails object configured with the properties from the application.yml file
*/
#Bean
#ConfigurationProperties("google.client")
OAuth2ProtectedResourceDetails google() {
return new AuthorizationCodeResourceDetails();
}
/*
/Returns a new ResourceServerProperties object configured with the properties from the application.yml file
*/
#Bean
#ConfigurationProperties("google.resource")
ResourceServerProperties googleResource() {
return new ResourceServerProperties();
}
}
Thanks in advance,
Dan

Allow OPTIONS HTTP Method for oauth/token request

I'm trying to enable oauth2 token fetching for my angular application. My configuration is working fine (authentication is working correctly for all requests, token fetching is working fine as well) but there is one problem.
CORS requests require that before GET an OPTIONS request is sent to the server. To make it worse, that request does not contain any authentication headers.
I would like to have this request always returning with 200 status without any authentication done on the server. Is it possible? Maybe I'm missing something
my spring security config:
#Configuration
#EnableWebSecurity
#EnableAuthorizationServer
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
#Inject
private UserService userService;
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
#Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
return new DefaultWebResponseExceptionTranslator() {
#Override
public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
OAuth2Exception body = responseEntity.getBody();
HttpHeaders headers = new HttpHeaders();
headers.setAll(responseEntity.getHeaders().toSingleValueMap());
headers.set("Access-Control-Allow-Origin", "*");
headers.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
headers.set("Access-Control-Max-Age", "3600");
headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
return new ResponseEntity<>(body, headers, responseEntity.getStatusCode());
}
};
}
#Bean
public AuthorizationServerConfigurer authorizationServerConfigurer() {
return new AuthorizationServerConfigurer() {
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
security.authenticationEntryPoint(oAuth2AuthenticationEntryPoint);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("secret-client")
.secret("secret")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_LOGIN")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(60 * 60 * 12); // 12 hours
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(tokenServices());
endpoints.authenticationManager(authenticationManager());
}
};
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.warn("FIX ME: REMOVE AFTER DEBUG!!!!!!!!!!!!");
log.debug("authenticate: " + authentication.getPrincipal() + ":" + authentication.getCredentials());
final Collection<GrantedAuthority> authorities = new ArrayList<>();
WomarUser user = userService.findUser(authentication.getPrincipal().toString(), authentication.getCredentials().toString());
for (UserRole userRole : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(userRole.getName()));
}
return new UsernamePasswordAuthenticationToken(user.getLogin(), user.getPassword(), authorities);
}
};
}
#Bean
public OAuth2AuthenticationManager auth2AuthenticationManager() {
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
oAuth2AuthenticationManager.setTokenServices(tokenServices());
return oAuth2AuthenticationManager;
}
#Bean
public OAuth2AuthenticationProcessingFilter auth2AuthenticationProcessingFilter() throws Exception {
OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
oAuth2AuthenticationProcessingFilter.setAuthenticationManager(auth2AuthenticationManager());
return oAuth2AuthenticationProcessingFilter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
oAuth2AuthenticationEntryPoint.setRealmName("realmName");
oAuth2AuthenticationEntryPoint.setTypeName("Basic");
oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
http
.antMatcher("/**").httpBasic()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.and().addFilterBefore(auth2AuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/rest/womar/admin/**").hasRole("ADMIN")
.antMatchers("/rest/womar/**").hasRole("USER");
}
}
angular request:
var config = {
params: {
grant_type: 'password',
username: login,
password: password
},
headers: {
Authorization: 'Basic ' + Base64.encode('secret-client' + ':' + 'secret')
}
};
$http.get("http://localhost:8080/oauth/token", config)
.success(function(data, status) {
$log.log('success');
$log.log(data);
$log.log(status);
})
.error(function(data, status) {
$log.log('error');
$log.log(data);
$log.log(status);
});
#EnableAuthorizationServer is adding http security configuration for endpoints like /oauth/token, /oauth/token_key etc at order 0. So what you should do is to define a http security rule for /oauth/token endpoint only for the OPTIONS http method which is at a higher order.
Something like this:
#Order(-1)
#Configuration
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
}
}
I was using the solution proposed by idursun. The OPTION call started to work, but still had problems with Access-Control-Allow-Origin.
This filter implementation definitively worked for me:
Standalone Spring OAuth2 JWT Authorization Server + CORS
I just add
#Order(Ordered.HIGHEST_PRECEDENCE)
in
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {....}
and config the support of spring
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Worked for me.
Same problem with Spring-Boot 1.4.7.RELEASE
My WebSecurityConfigurerAdapter was using SecurityProperties.ACCESS_OVERRIDE_ORDER so, selected answer did not work.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter
Thus, I added the following filter configuration with preceding order:
#Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(corsConfigurationSource()));
bean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER);
return bean;
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return source;
}
and it got the job done.
Note: equivalent result can be achieved with a javax.servlet.Filter bean with #Order(SecurityProperties.DEFAULT_FILTER_ORDER) annotation as below:
#Component
#Order(SecurityProperties.DEFAULT_FILTER_ORDER)
public class CorsFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin" , "*" );
response.setHeader("Access-Control-Allow-Methods" , "POST, PUT, GET, OPTIONS, DELETE" );
response.setHeader("Access-Control-Allow-Headers" , "Authorization, Content-Type" );
response.setHeader("Access-Control-Max-Age" , "3600" );
if("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
}
else {
chain.doFilter(req, res);
}
}
// ...
}
The following works for Spring Boot 2. It does not pick up other CORS configurations otherwise.
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// this is a Spring ConfigurationProperty use any way to get the CORS values
#Autowired
private CorsProperties corsProperties;
// other things
//...
#Override
public void configure(
AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
if (corsProperties.getAllowedOrigins() != null) {
Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
Arrays.asList(corsProperties.getAllowedOrigins().split(",")).stream()
.filter(StringUtils::isNotBlank).forEach(s -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(s.trim());
if (corsProperties.getAllowedMethods() != null) {
config.setAllowedMethods(Arrays.asList(corsProperties.getAllowedMethods().split(",")));
}
if (corsProperties.getAllowedHeaders() != null) {
config.setAllowedHeaders(Arrays.asList(corsProperties.getAllowedHeaders().split(",")));
}
// here the /oauth/token is used
corsConfigMap.put("/oauth/token", config);
});
endpoints.getFrameworkEndpointHandlerMapping()
.setCorsConfigurations(corsConfigMap);
}
}
}
And in addition the already mentioned allowance of the OPTIONS request:
#Order(-1)
#Configuration
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
authorizeRequests()
.antMatchers("/**/oauth/token").permitAll()
.and().httpBasic().realmName(securityRealm)
// would throw a 403 otherwise
.and().csrf().disable()
// optional, but with a token a sesion is not needed anymore
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

Resources