I wrote functionality using Spring Security SwitchUserFilter. In application I can switch user using /j_spring_security_switch_user?j_username=xxx URL and go back to previous using /j_spring_security_exit_user.
I also implemented several methods that depends on fact of switching user, so I want to write unit tests for them.
Therefore my question is how can I switch user in jUnit tests environment?
I wrote method which is preparing user with SwitchUserGrantedAuthority and log him in. It seems working fine for my testing purposes, but any tips and comments would be very appreciated.
#SuppressWarnings({ "rawtypes", "unchecked" })
private User logAdminAsUser(User admin, String roleName) {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(admin, null, "ROLE_ADMIN"));
Authentication adminAuth = SecurityContextHolder.getContext().getAuthentication();
SwitchUserGrantedAuthority switchUserGrantedAuthority =
new SwitchUserGrantedAuthority("ROLE_ADMIN", adminAuth);
List authorities = new LinkedList();
authorities.add(switchUserGrantedAuthority);
User user = populator.storeUser("ROLE_USER");
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(user, null, authorities));
return user;
}
If you want an integrational test, you should consider using a custom http client, or if your test logic depends on it, even GUI drivers like Selenium.
If we are talking about unit tests, refer to Springs
http://spring.io/blog/2014/05/07/preview-spring-security-test-method-security
documentation, they support testing heavily, #WithMockUser annotation appears to be what you are looking for, it allows you to specify with which role or user this test should be runned.
I used this:
private void switchUser(User user, String roleName)
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<GrantedAuthority> authorities =
new ArrayList<>();
GrantedAuthority ga = new SimpleGrantedAuthority(roleName);
authorities.add(ga);
Authentication result = new UsernamePasswordAuthenticationTokenExt(
user,
authentication.getCredentials(),
null,
System.currentTimeMillis()
);
SecurityContextHolder.getContext().setAuthentication( result );
}
where User is the new user, and the roleName is the new authority to set (of course this method can be modified get more params, etc.)
Related
I am trying to implement SSO in our app using keycloak-spring-security-adapter. The logging itself is working fine, but inside the app we have modules availability based on user roles/groups and i am not able to get user roles from SecurityContext to show users only what they should see.
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null) {
KeycloakPrincipal principal = (KeycloakPrincipal) context.getAuthentication().getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
logger.info("KEYCLOAK ROLES: " + realmAccess.getRoles());
above logger for my user always gives this:
KEYCLOAK ROLES: [offline_access, uma_authorization]
And these are not the roles registered in keycloak server, because the one used for authenticating my user is:
GSAP_APPLICATION_SUPPORT
I am not able to log into the app with user that is not a member of any keycloak-registered groups so thats why i know this process works fine.
Is there a way of getting list of current user roles from keycloak based on userId/token?
Hardcoding the roles checking inside the service is not a best practice, it's a common approach to divide role based functionalities by API like:
api/v1/admin/**, api/v1/user/**
Using this you can restrict the access to API by roles:
http.authorizeExchange()
.pathMatchers("your_endpoint").hasAnyRole("desired_role");
PS Please pay attention that keycloak adds the "ROLE_" prefix to the rolename, so you can
use ROLE_admin, ROLE_user in your configuration
or
use role names without "ROLE_" prefix (admin, user), and implement the JWT auth converter(example for Reactive (webFlux), you can do similar for Tomcat):
:
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> getJwtAuthenticationConverter() {var converter = new ReactiveJwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
Collection<String> roles = (Collection<String>) realmAccess.get("roles");
return Flux.fromIterable(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList());
});
return converter;
}
I've been following this tutorial in order to create an Authentication Server, but I'm facing some problems regarding the concepts, I guess.
Look, when I register a Client in Repository, I have to define some parameters, like its id, secret, authentication method, grant types, redirection uris and scopes:
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
When I'm back to my Resource Server, I find that my client was successfully logged in and it returns with an "articles.read" scope. Everything is fine here, supposing that I want to protect my endpoints with the Client's scope, but this is not my case.
In my situation, I want to protect my endpoints according to my User's role in database.
I'll give you an example, so you don't have to read the whole Baeldung's website:
I try to access: http://localhost:8080/articles.
It redirects to: http://auth-server:9000, where a Spring Security Login Form appears.
When you submit the proper credentials (which are compared from a database using the default Spring Security schema), it basically gets you back to: http://localhost:8080/articles.
Well, in that point, I have an Authorization Token with the Client scope, but not the logged User role.
Is there an standard way to configure my project to achieve this or, do I have to think of a creative way to do so?
Thank you in advance.
For role based authentication you should map authorities in Oauth token.
OAuth2AuthenticationToken.getAuthorities() is used for authorizing requests, such as in hasRole('USER') or hasRole('ADMIN').
For this you need to implement the userAuthoritiesMapper, something like this:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}
In Java tests, #WithMockUser initialize the current user to a fake user. How to modify this user inside my test method (so after the mocked user is created) ?
Example
#Test
#WithMockUser(roles = {"USER"})
void myTest() {
// add to the current user the "ADMIN" role
}
The final aim is to use #ParameterizedTest to run a test one time per authentication provided in parameter (for example: run the same test for "ADMIN" user, for "USER" user and for "MANAGER" user).
I've tried this:
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ADMIN"));
Authentication authentication = new UsernamePasswordAuthenticationToken("username", "password", authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
But the spring context seems to not reload, so my current user has no "ADMIN" role if I check it after those lines.
I have been following Baeldung's Spring 2FA tutorial to implement 2FA. I have created a CustomAuthenticationProvider as instructed, however it's not behaving as expected.
The odd thing is that after login, a username format that I'm not familiar with is displayed when using Principal.getName():
com.appname.models.User#69080b62
As parts of the application rely on this for fetching details, this isn't appropriate but I'm struggling to understand where I've gone wrong. I have done some research but without the correct nomenclature and the name of the format, I'm struggling to find the appropriate result.
public Authentication authenticate(Authentication auth) throws AuthenticationException {
User user = userRepository.findByUsername(auth.getName());
if(user == null) {
throw new BadCredentialsException("Invalid username or password");
}
if(user.getTwoFactor()) {
//as per tutorial
}
//this returns the "correct" username
System.out.println(user.getUsername());
final Authentication result = super.authenticate(auth);
//I suspect it's here that the issue is occurring, though any pointers in the right direction would be appreciated
return new UsernamePasswordAuthenticationToken(user, result.getCredentials(), result.getAuthorities());
}
I'm expecting the actual username rather than...however it is currently being returned - i.e. an email address of a user.
I have "solved" the problem by changing the last few lines to:
final Authentication result = super.authenticate(auth);
UserDetails userDetails = userDetailsService.loadUserByUsername(auth.getName());
return new UsernamePasswordAuthenticationToken(userDetails,
result.getCredentials(), userDetails.getAuthorities());
...where userDetailsService points to a simple implementation of the Spring Security UserDetailsService which returns a Spring Security UserDetails object, like so:
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if(user == null) throw new UsernameNotFoundException(username);
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Role role : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), user.getEnabled(), user.getNonExpired(),
user.getCredentialsNonExpired(), user.getNonLocked(), grantedAuthorities);
}
This works elsewhere in my application so I figured it might work here as well. I believe I could have left the final argument as result.getAuthorities(). I think I can also refactor so I'm not hitting the database twice, but for now I'm just glad that it works.
I'm not 100% sure why my relatively simple User model would not return the username as the Principal name, it may be that there is some more work which should be done to my User object to explicitly mark the username String as the principal name.
If anyone is interested in any further updates, or can provide any more information for anyone else experiencing uncertainty on this issue, please leave a comment or provide another (likely better) answer.
The question is very simple. I'd like to restrict user access with same login from different machines/browsers: only one live user session is possible.
Apache shiro library is used for user authentification and managment.
Of course this could be done using simple synchornized maps and etc. But the question is: Has Apache Shiro special mechanisms for that or not?
Another variant of this question: how to reveice the list of all subjects who are logged in the system using apache shiro?
UPD:
To clarify my question. My desire is to have some code like this (I known, that there isn't such class exception, but the idea must be more clean):
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(login, password);
try {
currentUser.login(token);
} catch (AlreadyAuthenticatedException aae) {
errorMsg = "You should logoff on another machine!";
}
The Shiro sessions are stored in SessionDAO with sessionId as keys. Without extra effort you cannot access a session by a principal (user name). However, you could extend DefaultSecurityManager and check all active sessions by SessionDAO.getActiveSessions.
The following codes could be a simple example (suppose you are not using WebSubject):
public class UniquePrincipalSecurityManager extends org.apache.shiro.mgt.DefaultSecurityManager {
#Override
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
String loginPrincipal = (String) token.getPrincipal();
DefaultSessionManager sm = (DefaultSessionManager) getSessionManager();
for (Session session : sm.getSessionDAO().getActiveSessions()) {
SimplePrincipalCollection p = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (p != null && loginPrincipal.equals(p.getPrimaryPrincipal())) {
throw new AlreadyAuthenticatedException();
}
}
return super.login(subject, token);
}
}