How to validate user roles from request header with spring security configuration - spring

I have added spring security with taglibs for role based content loading in my application . The authentication process will be taken care by external service and after successful authentication , the external service will add user detail and role detail in the request header. In my application I have to get the roles from request header and need to validate the roles with spring security configure method.
Help me on how to validate the roles .
Spring security Class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/console")
.access("hasRole('MASTER ADMIN') or hasRole('ADMIN') or hasRole('DEV') or hasRole('QA')")
.and()
.formLogin()
.defaultSuccessUrl("/console", true)
.and().logout().logoutSuccessUrl("/login");
}
}
Controller Class:
#RequestMapping(value = "/login")
public ModelAndView validateLogin(final HttpServletRequest request, final HttpServletResponse response) {
final ModelAndView modelView = new ModelAndView();
final String loginUserId = request.getParameter("USER");
final String companyCode = request.getHeader("ROLE");
final String firstName = request.getHeader("FIRSTNAME");
final String lastName = request.getHeader("LASTNAME");
//** some code to validate the role details with spring security configure method ie (has access method) and return the default success url based on the role.

Related

How can I do to my spring boot resource server oauth 2 get user's extra data from api when user authenticate e keep it into security context?

I have a resource server done with Spring Boot. I'm using Spring Security 5.3 to authenticate and authorize the frontend exchange data. I've configured a authorization server "issuer-uri" in application.yml that provides and validates the access_token (jwt).
Until there ok. The problem that authorization server doesn't provide at once every user's information that I need in access_token or id_token. With the sub claim and access_token I need to do a request to endpoint to get more extra data about user.
I would like to know how can I do a request to get that information just when the user authenticates and put them into security context togheter the information that's already comes. So that way, I could get that information in some service when needed without make a request to endpoint each time:
SecurityContextHolder.getContext().getAuthentication().getDetails()
It's here my WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String CLAIM_ROLES = "role";
private static final String AUTHORITY_PREFIX = "ROLE_";
#Value("${sso.issuers_uri}")
private String issuers;
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get);
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] result = issuers.split(",");
List<String> arrIssuers = Arrays.asList(result);
arrIssuers.stream().forEach(issuer -> addManager(authenticationManagers, issuer));
http
.httpBasic().disable()
.formLogin(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(auth -> auth
.antMatchers(
"/*",
"/signin-oidc",
"/uri-login_unico",
"/assets/**","/views/**",
"index.html",
"/api/segmentos/listar_publicados",
"/api/modelos",
"/api/modelos/*"
).permitAll()
.antMatchers(
"/api/admin/**"
).hasRole("role.PGR.Admin")
.antMatchers(
"/api/govbr/**"
).hasAnyAuthority("SCOPE_govbr_empresa")
.anyRequest().authenticated()
).oauth2ResourceServer(oauth2ResourceServer -> {
oauth2ResourceServer.authenticationManagerResolver(this.authenticationManagerResolver);
});
}
public void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(JwtDecoders.fromOidcIssuerLocation(issuer));
authenticationProvider.setJwtAuthenticationConverter(getJwtAuthenticationConverter());
authenticationManagers.put(issuer, authenticationProvider::authenticate);
}
private Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(getJwtGrantedAuthoritiesConverter());
return jwtAuthenticationConverter;
}
private Converter<Jwt, Collection<GrantedAuthority>> getJwtGrantedAuthoritiesConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix(AUTHORITY_PREFIX);
converter.setAuthoritiesClaimName(CLAIM_ROLES);
return converter;
}
}
I don't know if I need to do a custom AuthenticationManger or if I can do this with a security filter after authenticated. If someone could help me, I really apprecite it. Tks!!!

isAuthenticated annotation does not prevent access

I have a following controller:
#RestController
#RequestMapping("/payments")
public class PaymentController {
#Autowired
PaymentService paymentService;
#Autowired
private Environment env;
#PostMapping("/create")
#PreAuthorize("isAuthenticated()")
public ResponseEntity<String> create(#Valid #RequestBody DownPayment downpayment) {
Customer customer;
Charge charge;
User user = new User();
............
}
}
WebSecurity config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
I want to use preAuthorize annotation (method level) instead of http security. The payments/create endpoint is publicly accessible which works without throwing any unauthorised error.
Set a breakpoint and check what is contained in the SecurityContextHolder, e.g. like that: SecurityContextHolder.getContext().getAuthentication(). I suggest you add what is contained in the SecurityContextHolder to your question so that people can help you better.
My assumption is that you have anonymous access enabled, which means that an anonymous authentication object is placed in the SecurityContextHolder if no other authentication was set (e.g. by a AuthenticationTokenFilter). Spring detects this as an authentication, so that the access to your API is not prevented by the #PreAuthorize("isAuthenticated()") annotation. Generally you should consider if it might not be better to use role-based access rules, as these are more fine-granular.
You can disable anonymous access as follows:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.csrf().disable();
}

Understanding Spring Security #EnableOAuth2Client annotation

I'm having a problem implementing OpenID connect built on Spring Security Oauth2 library. (Read more about the problem in a separate question.) While researching it, I read the documentation for the #EnableOauth2Client annotation, which says:
Enable configuration for an OAuth2 client in a web application that uses Spring Security and wants to use the Authorization Code Grant from one or more OAuth2 Authorization servers. To take advantage of this feature you need a global servlet filter in your application of the DelegatingFilterProxy that delegates to a bean named "oauth2ClientContextFilter". Once that filter is in place your client app can use another bean provided by this annotation (an AccessTokenRequest) to create an OAuth2RestTemplate, e.g.
#Configuration
#EnableOAuth2Client
public class RemoteResourceConfiguration {
#Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
return new OAuth2RestTemplate(remote(), oauth2ClientContext);
}
}
Client apps that use client credentials grants do not need the AccessTokenRequest or the scoped RestOperations (the state is global for the app), but they should still use the filter to trigger the OAuth2RestOperations to obtain a token when necessary. Apps that us [sic] password grants need to set the authentication properties in the OAuth2ProtectedResourceDetails before using the RestOperations, and this means the resource details themselves also have to be per session (assuming there are multiple users in the system).
A Note About Versions and Documentation: this documentation is the 2.0.4 release, which is all that is linked to from the Spring Security project page even for the newer 2.3.5 link, which my project is using. Our other Spring versions: Spring Boot 1.3.0, Spring Security 3.2.5, Spring Framework 4.2.3.
I don't understand quite what it means, particularly
a global servlet filter in your application of the DelegatingFilterProxy that delegates to a bean named "oauth2ClientContextFilter"
Here is how we are configuring our rest template.
#Configuration
#EnableOAuth2Client
public class OpenIdConnectConfig {
#Bean
public OAuth2ProtectedResourceDetails openIdResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setScope(oidcScopes);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
#Bean(name = "my.company.ui.security.OpenIdRestTemplate")
// ToDo: fix org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread
public OAuth2RestTemplate OpenIdRestTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(openIdResourceDetails(), clientContext);
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.clientSecret}")
private String clientSecret;
#Value("${oidc.accessTokenUrl}")
private String accessTokenUri;
#Value("${oidc.userAuthorizationUri}")
private String userAuthorizationUri;
#Value("${oidc.redirectUri}")
private String redirectUri;
#Value("#{'${oidc.scopes}'.split(',')}")
private List<String> oidcScopes;
}
The filter that performs the authentication (some exception handling and user processing code removed):
public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
public OpenIdConnectFilter(
RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationService authenticationService
) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(new NoopAuthenticationManager());
}
#SuppressWarnings("RedundantThrows") // Matching overridden method
#Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException, IOException, ServletException {
// Required parameters (one-time access code, state) are retrieved from the context
OAuth2AccessToken oAuth2AccessToken = restTemplate.getAccessToken();
// Process the token, get the user details, return an Authentication object.
}
public void setRestTemplate(OAuth2RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private static class NoopAuthenticationManager implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdConnectFilter.class);
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.issuer}")
private String issuer;
#Value("${oidc.jwt.jwk.url}")
private String jwkUrl;
private final AuthenticationService authenticationService;
private OAuth2RestTemplate restTemplate;
}
And the Security Config that sets up the Spring Security FilterProxyChain:
#Configuration
#EnableWebSecurity
#EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
#SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(new OAuth2ClientContextFilter(), SecurityContextPersistenceFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
You'll notice multiple authentication mechanisms in there. We're in the process of migrating from Form Login to Oauth login and there is a feature flag in place for the initial release. The flag is working quite well, except for the problem described in my linked question at the top that occurs for a while after flipping the flag, then seems to resolve itself.
Is the configuration I've shown above sufficient fulfillment of the documented instructions for the #EnableOauth2Client annotation?
Or is there something else I need to do with a DelegationFilterProxy? If so, how?

How to set Manually Authenticate User with Spring boot Security

I Created Spring boot application with SSO login. I have used saml.xml file for it. After SSO login i called getAuthentication() method it will return annonymousUser
every time. I want to get SSO Logged User details.
Principal principal =
SecurityContextHolder.getContext().getAuthentication();
Security configuration class look like following :
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/actuator").authenticated();
http.headers().cacheControl().disable();
http.csrf().disable();
http.logout().logoutSuccessUrl("/assets/logout.html");
}
}
You can make user login into spring security like below.
public void login(HttpServletRequest req, String user, String pass) {
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(user, pass);
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
}
Refer manually-set-user-authentication-spring-security

How to redirect UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders?

Using Spring Security 4.02, can anyone help with some tips on how I can handle UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders so that authenticated requests, with the correct header, but which are unauthorized, are sent to a specific URL instead of the forms-login page?
Let me explain further what I'm trying to accomplish for accessing a web app being secured by SSO behind a proxy. Not all users who are authenticated by SSO will have access to this app. So I need to account for 3 access scenarios:
authenticated user (header is present) is authorized (username/roles are present in app's db)
authenticated user (header is present) is unauthorized (username/roles are not present in app's db)
unauthenticated user with username/roles present in app's db
The actions when accessing the website should be:
authenticated/authorized user proceeds directly to target URL
authenticated/unauthorized user is redirected to error/info page
unauthenticated user is redirected to forms-login page for authentication
With my current configuration, scenarios 1 & 3 appear to be working as desired. For scenario 2 I've tried setting RequestHeaderAuthenticationFilter#setExceptionIfHeaderMissing to both true and false.
If setExceptionIfHeaderMissing=false, authenticated/unauthorized request is handled by ExceptionTranslationFilter where AccessDeniedException is thrown and user is redirected to forms-login page.
If setExceptionIfHeaderMissing=true, authenticated/unauthorized request encounters PreAuthenticatedCredentialsNotFoundException from AbstractPreAuthenticatedProcessingFilter.doAuthenticate and HTTP 500 is returned.
So I've read and reread the Spring Security reference and api documents and scoured the web and just can't quite figure out what I need to do. I think I somehow need to enable some kind of filter or handler to trap the PreAuthenticatedCredentialsNotFoundException with a redirected response. But I can't seem to wrap my head around how to implement that with all the spring tools available. Can someone please offer some specifics? Many thanks in advance!!
Here is my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String AUTHENTICATION_HEADER_NAME = "PKE_SUBJECT";
#Autowired
CustomUserDetailsServiceImpl customUserDetailsServiceImpl;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
auth.userDetailsService(customUserDetailsServiceImpl);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and()
.authorizeRequests()
.antMatchers("/javax.faces.resource/**", "/resources/**", "/templates/**", "/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/public/welcome.xhtml")
.and()
.addFilter(requestHeaderAuthenticationFilter());
}
#Bean PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() throws Exception {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return provider;
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader(AUTHENTICATION_HEADER_NAME);
filter.setAuthenticationManager(authenticationManagerBean());
filter.setExceptionIfHeaderMissing(true);
return filter;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>
userDetailsServiceWrapper() throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper
= new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
wrapper.setUserDetailsService(customUserDetailsServiceImpl);
return wrapper;
}
}
My customized UserDetailsService:
#Service("customUserDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetailDO userDetail = userRepo.getUserDetailById(username);
if(userDetail == null) {
throw new UsernameNotFoundException("user is not authorized for this application");
}
List<UserRoleDO> roles = userRepo.getRolesByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if(CollectionUtils.isNotEmpty(roles)) {
for(UserRoleDO role : roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRole());
authorities.add(authority);
}
}
UserDetails user = new User(username, "N/A", authorities);
return user;
}
}
I realized that I did not need to handle the exception. What I did was to shift my thinking on this. I realized that even if the username was not found by the customUserDetailsService, the request was still an authenticated request since the request is trusted to be authenticated by the SSO and the proxy server.
So instead of returning a UsernameNotFoundException I returned the org.springframework.security.core.userdetails.User with an empty Authorities collection. And because the RequestHeaderAuthenticationFilter.setExceptionIfHeaderMissing = false by default, no exception is thrown and then the authenticated request is passed to the access filter where it is determined that the request has no authorization to access any resources. So instead of redirecting to the next authentication filter which would be the forms login provider, a 403 Access Denied http status is returned which I can then override to redirect to a user-friendly error page.

Resources