My spring webflux security code is,
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http) {
http.securityContextRepository(securityContextRepository);
return http.authorizeExchange().matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.pathMatchers(props.getSecurity().getIgnorePatterns()).permitAll().anyExchange().authenticated()
.and().formLogin()
.and().exceptionHandling()
.authenticationEntryPoint((exchange, exception) -> Mono.error(exception))
.accessDeniedHandler((exchange, exception) -> Mono.error(exception))
.and().build();
}
Now, I have the below code to get the logged in user details.
public Mono<AppUserDetails> getUser() {
Mono<Principal> principalMono = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).cast(Principal.class);
principalMono.flatMap(principal -> {
if (principal instanceof AuthenticatedUserToken) {
final AppUserDetails user = ((AuthenticatedUserToken<?>) principal).getUser();
return Mono.just(user);
}
return Mono.error(() -> new IllegalArgumentException("Invalid access"));
}).switchIfEmpty(Mono.defer(() -> {
return Mono.empty();
}));
}
I have an API to create a project and the project table has audit column as well like createdBy user. I am using the above code( getUser() method ) to retrieve the logged in user and get userId from there.
Now, trying to test this API thru postman with just a mock user with userId 1 instead of logging in with the real user as the front end is not ready yet.
How can I run the spring boot app with mock user and return mock user id when the getUser() method is invoked so that I can test end to end.
I am able to achieve this with the code below.
Created a conditional based class for Mock user.
#ConditionalOnProperty(prefix = "admin.service", value = "security.mode", havingValue = "MOCK")
#EnableWebFluxSecurity
public class MockSecurityConfig {
#Autowired
private AppProperties appProps;
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http) {
return http.authorizeExchange().pathMatchers("/**").permitAll().and().csrf().disable().build();
}
#Bean
public AuthenticatedPrinciplaProvider mockSecurityPrincipalProvider() {
return new MockSecurityContextPrincipleProvider(props.getSecurity().getMock());
}
}
Related
I have created a sample project which can demonstrate SAML 2 SSO capabilities with saml providers such as Azure AD and Okta.
I was able to configure both of above providers at once in spring configuration by using RelyingPartyRegistrationRepository and both of them are working as expected.
#Bean
protected RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
List<RelyingPartyRegistration> registrationList = new ArrayList<>();
registrationList.add(oktaRegistration);
registrationList.add(azureRegistration);
return new InMemoryRelyingPartyRegistrationRepository(registrationList);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize.antMatchers("/").permitAll().anyRequest().authenticated()
).saml2Login();
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrations());
Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
return http.build();
}
I would like to know whether there is any way to create RelyingPartyRegistrationRepository dynamically once the application fully started. The requirement is to take the SAML metadata file from user in some sort of a form upload and then create RelyingPartyRegistrationRepository based on it.
The issue is, RelyingPartyRegistrationRepository is a Spring bean which is used by the Spring security internals. In this case even though we could create new RelyingPartyRegistrationRepository instances, will Spring security take them dynamically?
You will not create multiple RelyingPartyRegistrationRepository, you will create your custom implementation of RelyingPartyRegistrationRepository that accepts adding new entries to it. A simple example:
#Service
public class MyRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final List<RelyingPartyRegistration> registrations = new ArrayList<>();
public MyRelyingPartyRegistrationRepository() {
addDefaultRegistrations();
}
private void addDefaultRegistrations() {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
add(oktaRegistration);
add(azureRegistration);
}
#Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
for (RelyingPartyRegistration registration : this.registrations) {
if (registration.getRegistrationId().equals(registrationId)) {
return registration;
}
}
return null;
}
public void add(RelyingPartyRegistration newRegistration) {
this.registrations.add(newRegistration);
}
}
And then in a Controller, for example, you can autowire this dependency and add new registrations to it:
#RestController
public class SamlController {
private final MyRelyingPartyRegistrationRepository repository;
#PostMapping("/registration")
public void addRegistration(/* receive it somehow */) {
this.repository.add(theRegistration);
}
}
I am trying to secure demo application with jwt token based authentication. My usecase is I have two type of users one is teacher which is admin and other is student which is normal user.
I want to configure a security for teacher and student separately. I have different sources for user authentication — one for teachers and one for normal users. If have configured two webseconfigadapters.
In StudentWebConfigAdapter
config() {
http.antMatcher("/student/getmarks/{id}")
.authorizeRequests().anyRequest().hasRole("USER")
}
#Bean("studentAuth")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
In TeacherWebConfigAdapter
config() {
http.antMatcher("/student/**","/teacher/**")
.authorizeRequests().anyRequest().hasRole("ADMIN")
}
#Bean("teacherAuth")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
How do I configure seperate authentication manager for both. I tried by creating separate bean with different names. it is giving error authentication manager should be only one.
Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found [studentAuth, teacherAuth]
Maybe its help for you..
set your group for page in secconfig:
.antMatchers("/page").hasAnyAuthority("USER,ADMIN").anyRequest()
and than you can work with groups in thymeleaf:
<div sec:authorize="hasAuthority('ADMIN')"></div>
or in controller:
#RequestMapping(value = {"/index"}, method =RequestMethod.GET)
public String index(Model model, Principal principal) {
Collection<?> auth =
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
if(auth.contains(new SimpleGrantedAuthority("ADMIN"))
{
return "pages/indexForUser";
}
else if(auth.contains(new SimpleGrantedAuthority("USER")))
{
return "pages/indexForTeacher";
}
//ROLE_ANONYMOUS
}
I am currently implementing a SAML SSO solution in my application where in my SAMLUserDetailsService, I am loading my user
#Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
return new User(credential.getNameID().getValue());
}
}
I am then using a SavedRequestAwareAuthenticationSuccessHandler to redirect user to a landing controller upon successful authentication.
#Bean
public AuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Controller:
#RequestMapping("/landing")
public ResponseEntity landing(User user) {
return ResponseEntity.ok(user.getLoginName());
}
Is there a way to pass the User object to my controller. I noticed that this is usually done using a HandlerMethodArgumentResolver but since my application is stateless and does not use sessions, is there a way to achieve this using another way please?
You don't need injection for this. Use following instead:
SecurityContextHolder.getContext().getAuthentication().getName()
or
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
In the latter case check the type of what getPrincipal() returned. It can be String, it can be UserDetails. If latter, cast it to UserDetails and call getUsername().
I have issues with using the Pre/Post Authorization Annotations from Spring Security and the Servlet API with Keycloak integration. I investigated a lot of articles, tutorials and the following questions without further luck:
Obtaining user roles in servlet application using keycloak
Spring Boot Keycloak - How to get a list of roles assigned to a user?
Using spring security annotations with keycloak
Spring Boot + Spring Security + Hierarchical Roles
How do I add method based security to a Spring Boot project?
Configure DefaultMethodSecurityExpressionHandler using Spring Security Java Config
SpringBoot + method based hierarchical roles security: ServletContext is required
All I want is removing the ROLES_ prefix, use hierarchical roles and a comfortable way to retrieve the users' roles.
As of now, I am able to retrieve a hierarchical role like this in a Controller but cannot use the annotations:
#Controller
class HomeController {
#Autowired
AccessToken token
#GetMapping('/')
def home(Authentication auth, HttpServletRequest request) {
// Role 'admin' is defined in Keycloak for this application
assert token.getResourceAccess('my-app').roles == ['admin']
// All effective roles are mapped
assert auth.authorities.collect { it.authority }.containsAll(['admin', 'author', 'user'])
// (!) But this won't work:
assert request.isUserInRole('admin')
}
// (!) Leads to a 403: Forbidden
#GetMapping('/sec')
#PreAuthorize("hasRole('admin')") {
return "Hello World"
}
}
I am guessing that the #PreAuthorize annotation does not work, because that Servlet method is not successful.
There are only three roles - admin, author, user - defined in Keycloak and Spring:
enum Role {
USER('user'),
AUTHOR('author'),
ADMIN('admin')
final String id
Role(String id) {
this.id = id
}
#Override
String toString() {
id
}
}
Keycloak Configuration
Upon removing the #EnableGlobalMethodSecurity annotation from this Web Security reveals an Error creating bean with name 'resourceHandlerMapping' caused by a No ServletContext set error - no clue, where that comes from!
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider().tap { provider ->
// Assigns the Roles via Keycloaks role mapping
provider.grantedAuthoritiesMapper = userAuthoritiesMapper
})
}
#Bean
RoleHierarchyImpl getRoleHierarchy() {
new RoleHierarchyImpl().tap {
hierarchy = "$Role.ADMIN > $Role.AUTHOR > $Role.USER"
}
}
#Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new RoleHierarchyAuthoritiesMapper(roleHierarchy)
}
SecurityExpressionHandler<FilterInvocation> expressionHandler() {
// Removes the prefix
new DefaultWebSecurityExpressionHandler().tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
// ...
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
AccessToken accessToken() {
def request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
def authToken = (KeycloakAuthenticationToken) request.userPrincipal
def securityContext = (KeycloakSecurityContext) authToken.credentials
return securityContext.token
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http)
http
.authorizeRequests()
.expressionHandler(expressionHandler())
// ...
}
}
Global Method Security Configuration
I needed to explicitly allow allow-bean-definition-overriding, because otherwise I got a bean with that name already defined error, which reveals that I completely lost control over this whole situation and don't know what's goin on.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
RoleHierarchy roleHierarchy
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
((DefaultMethodSecurityExpressionHandler)super.createExpressionHandler()).tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
}
Any further configurations that could be important? Thanks a lot for your help!
As M. Deinum pointed out, one must remove the defaultRolePrefix in multiple places with a BeanPostProcessor, which is explained in (docs.spring.io) Disable ROLE_ Prefixing.
This approach seemed not very clean to me and so I wrote a custom AuthoritiesMapper to achieve mapping hierarchical roles from Keycloak without the need to rename them to the ROLE_ Spring standard. First of all, the Roles enumeration was modified to conform that standard inside the application scope:
enum Role {
USER('ROLE_USER'),
AUTHOR('ROLE_AUTHOR'),
ADMIN('ROLE_ADMIN')
// ...
}
Secondly, I replaced the RoleHierarchyAuthoritiesMapper with a prefixing hierarchical implementation:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// ..
// Replaces the RoleHierarchyAuthoritiesMapper
#Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new PrefixingRoleHierarchyAuthoritiesMapper(roleHierarchy)
}
}
class PrefixingRoleHierarchyAuthoritiesMapper extends RoleHierarchyAuthoritiesMapper {
String prefix = 'ROLE_'
PrefixingRoleHierarchyAuthoritiesMapper(RoleHierarchy roleHierarchy) {
super(roleHierarchy)
}
#Override
Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
def prefixedAuthorities = authorities.collect { GrantedAuthority originalAuthority ->
new GrantedAuthority() {
String authority = "${prefix}${originalAuthority.authority}".toUpperCase()
}
}
super.mapAuthorities(prefixedAuthorities)
}
}
And lastly, I got rid of the GlobalMethodSecurityConfig.
Apart from suggestions provided in (docs.spring.io) Disable ROLE_ Prefixing, and suggestion provided by M. Deinum, one more modification is needed while using KeycloakWebSecurityConfigurerAdapter.
In configureGlobal method, grantedAuthoritiesMapper bean is set in the bean keycloakAuthenticationProvider. And in grantedAuthoritiesMapper, prefix can be set to anything you want, where the default value is "ROLE_".
The code goes as follows:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper grantedAuthoritiesMapper = new SimpleAuthorityMapper();
grantedAuthoritiesMapper.setPrefix("");
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
This solution works for me.
I am developing an Spring boot app following its oauth stantards. I was wondering if it is possible to catch some event just before the redirection to the client with the valid token.
I need this to include some extra info in the response. That would be possible? Thank you
In order to add add extra info on the token a better and more standard way is use the TokenEnhancer. It is an interface that give you the possibility of enhancing an access token before it is stored. I provide you a skeleton example below:
#Configuration
#EnableAuthorizationServer
class SecurityOAuth2AutorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.....
.tokenEnhancer(tokenEnhancer())
.approvalStoreDisabled();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
....
}
#Bean
public TokenEnhancer tokenEnhancer(){
return new YourTokenEnhancer ();
}
}
class YourTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("yourAdditionalKey", "yourAdditionalValue");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Update
considering your message if you want add info in an header you can use an interceptor and bind to the org.springframework.security.oauth2.provider.endpoint.TokenEndpoint class that is the class that you invoke for retrieve the token and add hear the your extra information.
However I discourage this approach in favor of a more standard way and consider to use the TokenEnhancer that is the standard way for add extra info on your token.
Update
considering the comments I can suggest to implements your audit logic with an aspect in your authentication server the aspect can be like below:
#Aspect
#Component
class AuditLogger {
#AfterReturning("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public void aspect(JoinPoint joinPoint) {
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("success");
System.out.println(params);
// your audit logic in case of successful login
}
#AfterThrowing(value = "execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))", throwing = "error")
public void error(JoinPoint joinPoint, Exception error) {
System.out.println(error);
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("error");
System.out.println(params);
// your audit logic in case of failure login
}
}
I suggest to use an aspect instead of implements custom components of spring security because: first of all, audit is a cross cutting concern and an aspect is one of the best solution to achieve this and then, because customize Spring Security, especially for Oauth2, is a challeng and i do not advice it, in my experience is too complex and the effort do not repay the effort, the protocol is very complex and fill all use cases is a challenge. With an aspect that do the audit for you in he correct point is the best option for you.
I built an aspect that do audit on the TokenEndpoint.postAccessToken method, that is the code of Spring security oAuht2 that generate the token
The code of your interest in the Spring framework is below:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
....
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
....
}
I hope that it can help you