Please ensure that at least one realm can authenticate these tokens - java

So I have set up my shiro to have two Realms. A Username and Password Realm, using the standard UsernamePasswordToken. I have also set up a Custom Bearer Authentication Token that works off a token passed in from the user.
If i just use my passwordValidatorRealm it works find, if no user is found throws unknown account, if password doesn’t match throws incorrect credentials, perfect. But as soon as i put in my tokenValidatorRealm it throws a
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms.
In this instance my tokenValidatorRealm returns null as no token was provided, so it moves on to the passwordValidatorRealm and just breaks.
Any ideas why introducing a second Realm will cause my working passwordValidatorRealm to break?
Have tried with different authentication strategies, and no luck there.
Using shiro 1.2.2
EDIT
I have two implementations, one for password and one for token
Password:
public class PasswordAuthorizingRealm extends AuthenticatingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof UsernamePasswordToken) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
char[] password = usernamePasswordToken.getPassword();
if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm!");
}
//Null password is invalid
if (password == null) {
throw new AccountException("Null passwords are not allowed by this realm!");
}
UserService userService = new UserServiceImpl();
User user = userService.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException("Could not authenticate with given credentials");
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, user.getPassword(), "passwordValidatorRealm");
return simpleAuthenticationInfo;
} else {
return null;
}
}
}
and Bearer Token
public class TokenAuthorizingRealm extends AuthorizingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof BearerAuthenticationToken) {
BearerAuthenticationToken bearerAuthenticationToken = (BearerAuthenticationToken) authenticationToken;
String username = "" + bearerAuthenticationToken.getPrincipal();
User user = userService.getUserByUsername(username);
//User with such username has not found
if (user == null) {
throw new UnknownAccountException("Could not authenticate with given credentials");
}
BearerAuthenticationInfo bearerAuthenticationInfo = new BearerAuthenticationInfo(user);
return bearerAuthenticationInfo;
}
}
Shiro config
[main]
hashService = org.apache.shiro.crypto.hash.DefaultHashService
hashService.hashIterations = 500000
hashService.hashAlgorithmName = SHA-256
hashService.generatePublicSalt = true
hashService.privateSalt = ****
passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordService.hashService = $hashService
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService = $passwordService
authc = my.BearerTokenAuthenticatingFilter
tokenValidatorRealm = my.TokenAuthorizingRealm
passwordValidatorRealm = my.PasswordAuthorizingRealm
passwordValidatorRealm.credentialsMatcher = $passwordMatcher
securityManager.realms = $tokenValidatorRealm,$passwordValidatorRealm
These have been stripped out a bit, removed logging and other unnecessary code
The BearerTokenAuthenticatingFilter, just basically checks if a token has been supplied in the header if has
private void loginUser(ServletRequest request, ServletResponse response) throws Exception {
BearerAuthenticationToken token = (BearerAuthenticationToken) createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken "
+ "must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.sendRedirect("login");
}
}
BearerAuthenticationInfo class
public class BearerAuthenticationInfo implements AuthenticationInfo {
private final PrincipalCollection principalCollection;
private final User user;
public BearerAuthenticationInfo(User user) {
this.user = user;
this.principalCollection = buildPrincipalCollection(user);
}
public PrincipalCollection getPrincipals() {
return principalCollection;
}
public Object getCredentials() {
return user.getUsername();
}
private PrincipalCollection buildPrincipalCollection(User user) {
Collection<String> principals = new ArrayList<String>();
principals.add(user.getUsername());
return new SimplePrincipalCollection(principals, "tokenValidatorRealm");
}
}

Looks like it is expected behavior.
If you look at the javadoc for ModularRealmAuthenticator:
* #throws AuthenticationException if the user could not be authenticated or the user is denied authentication
* for the given principal and credentials.
*/
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
If you are having problems with the exception, you might need to change the code that calls the authentication to expect this exception.
Left for other searches:
You might have a missing supports method in your TokenAuthorizingRealm class.
Something like
#Override
public boolean supports(AuthenticationToken token) {
return token instanceof BearerAuthenticationToken;
}
should be present.

This discussion help me solve a similar problem. I wanted to authenticate a user by the application itself, not using any Shiro default implementation. To do that we must subclass AuthenticatingRealm, override doGetAuthenticationInfo and declare this realm as the validation one.
public class PasswordAuthorizingRealm extends AuthenticatingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
In Shiro.ini:
passwordValidatorRealm = my.PasswordAuthorizingRealm

Related

Spring security authentication manager exception

If I understand good, I have custom Authentication Manager class, and in there I check if someone in api pass correct credentials, but I wonder why it didn't throw exception while I passed empty username and password.
#Component
public class AuthManager implements AuthenticationManager {
private final DetailsService detailsService;
private final Logger logger = LoggerFactory.getLogger(AuthManager.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("credentials: " + authentication.getCredentials());
logger.info("principals: " + authentication.getPrincipal());
if (authentication.getCredentials() == null || authentication.getPrincipal() == null) {
throw new BadCredentialsException("Credentials are wrong");
}
UserDetails user = loadUser(authentication);
return new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
}
private UserDetails loadUser(Authentication auth) {
return detailsService.loadUserByUsername(auth.getPrincipal().toString());
}
That's filter
#Component
public class UsernamePasswordJsonFilter extends UsernamePasswordAuthenticationFilter {
private final ObjectMapper objectMapper;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public UsernamePasswordJsonFilter(ObjectMapper objectMapper, AuthManager manager,
AuthSuccessHandler success, AuthFailureHandler failure) {
this.objectMapper = objectMapper;
setAuthenticationSuccessHandler(success);
setAuthenticationFailureHandler(failure);
setAuthenticationManager(manager);
setFilterProcessesUrl("/login");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
LoginDTO authenticationRequest = objectMapper.readValue(request.getInputStream(), LoginDTO.class);
Authentication auth = new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(),
authenticationRequest.getPassword());
logger.info("UsernamePasswordJsonFilter");
return getAuthenticationManager().authenticate(auth);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
If I pass correct username and password it works, but I just wonder why it didn't throw exception when credentials are empty, also there is no exception thrown in console, in case someone asks for it
When you use
LoginDTO authenticationRequest = objectMapper.readValue(request.getInputStream(), LoginDTO.class);
It return LoginDTO with login = "" and password = "". It is not a null.
"" is not the same as null. If u want to be null in LoginDTO, request should not have username or password field at all
if (authentication.getCredentials() == null || authentication.getPrincipal() == null)
Here you also to check to empty string. Let's say "".equals(authentication.getCredentials()) or authentication.getCredentials().isEmpty()
same with authentication.getPrincipal()

Do I need a separate JWT Filter for Multiple Logins?

The User login is working well but I want to add a Customer Module to the project. I know that I need to write a custom UserDetails class to get the customer Username but I want to ask if I need to write another Custom JWT filter for the Customer Login validation. Presently this is the Filter class that I have for User Login. I have added a username and password field to the Customer entity.
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserAccountService myUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else {
System.out.println("Not Valid Token");
}
}
chain.doFilter(request, response);
}
}
As you can see the Filter is using the custom UserDetails to verify the username . How do I add the Customer userdetails service to the filter ? This is my first multiple login project please be lenient with me.
Differentiate between user and customer while logging. Accordingly, call the different service to get user details. More can be found here.
Spring Security user authentication against customers and employee
How do I add the Customer userdetails service to the filter?: inject it as you did with UserAccountService. If you do this way, you're using 1 filter (and of course, this filter is in 1 SecurityFilterChain), you could basically implement your filter like: trying to validate your user by myUserDetailsService and if it's not successful, continue with myCustomerDetailsService.
For multiple login project. The second way you could do is using 2 SecurityFilterChain. UserJwtFilter for 1 SecurityFilterChain and CustomJwtFilter for 1 SecurityFilterChain for example. People usually do this way for different login mechanisms Basic, OAuth2, SAML2. E.g:
Basic Authentication:
#Configuration
#Order(2)
public class BasicAuthenticationFilterChain extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/logout")
.and()
OAuth2 Authentication:
#Configuration
#Order(3)
public class OAuth2AuthenticationFilterChain extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/oauth")
.and()
In this case when a request with "/login" it'll be directed to BasicAuthenticationFilterChain, and a request with "/oauth" will go to OAuth2AuthenticationFilterChain. About Order: the lower is the higher priority and once the request's processed with a SecurityFilterChain, it won't go to another SecurityFilterChain. You can implement your project this way.
Conclusion: There are a lot of ways you can implement your idea with spring security, it depends on your choice.
it looks to me like you already did.
#Autowired
private UserAccountService myUserDetailsService;
But I would suggest using a Constructor instead of #Autowired. Spring will fill in the constructor parameters just the same. This could be very slim when you use the lombok library as well.
Using a constructor also makes mocking this a bit easier for testing.
Updated as discussed in the comments:
#Log //another lombok thing
#RequiredArgsConstructor
#Component
public class JwtRequestFilter extends Filter{
private final JwtTokenUtil jwtTokenUtil;
private final UserAccountService myUserDetailsService;
private final CustomerAccountService myCustomerDetailsService;
private static final String AUTH_HEADER = "authorization";
#Override
protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
String tokenHeader = ((HttpServletRequest) request).getHeader(AUTH_HEADER);
if(hasValue(tokenHeader) && tokenHeader.toLowerCase().startsWith("bearer ")){
jwtToken = requestTokenHeader.substring(7);
String username;
String jwtToken;
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
if (uSecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
if(isNull(userDetails)){
userDetails = myCustomerDetailsService.loadCustomerByUsername(username);
}
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
var token = createSecurityToken(userDetails);
SecurityContextHolder.getContext().setAuthentication(token);
} else {
throw new RuntimeException("Not a Valid Token.");
}
} else {
log.info("Authorization already present");
}
} catch (IllegalArgumentException e) {
throw new("Unable to get JWT Token",e);
} catch (ExpiredJwtException e) {
throw new("JWT Token has expired",e);
}
} else {
throw new RuntimeException("No valid authorization header found.");
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken createSecurityToken(UserDetails userDetails){
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
log.info("Authorities granted : {}", userDetails.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
return token;
}
}

Auditable Entity In Microservice Architecture

I'm trying to implement Abstract Auditable Entity in my current Microservice architecture. It is working fine for a single module but I'm confused on how to pass the SecurityContext across multiple modules.
I've already tried by transferring the token as a header from my zuul-service (auth-server) to other core modules and the value is always null.
Also, I tried passing the SecurityContext using feign client but it didn't work for me either.
Cannot get JWT Token from Zuul Header in Spring Boot Microservice Module
Audit Logging in Spring Microservices
Session Management in microservices
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
private static final int FILTER_ORDER = 0;
private static final boolean SHOULD_FILTER = true;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request1, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String header = request1.getHeader(jwtConfig.getHeader());
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request1, response);
return;
}
/* new token getting code*/
String token = header.replace(jwtConfig.getPrefix(), "");
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
System.out.println(username);
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username,
null, authorities.stream().map(
SimpleGrantedAuthority::new
).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
System.out.println(String.format("%s request to %s", request1.getMethod(), request1.getRequestURL().toString()));
/* return null;*/
request1.setAttribute("header",token);
chain.doFilter(request1, response);
}
}

Override the existing Spring Security authentication

How can I override the existing Spring Security authentication by invoking a Web Service and when it's failed, need to redirect some third party login page.
For calling this authentication web service, I need to get some ServletRequest parameter and for redirection, I need to access the ServletResponse.
Therefore I need to find out some Authentication method with ServletRequest and ServletResponse parameters.
But still, I failed to find out such a ProcessingFilter or AuthenticationProvider.
According to Spring Security basic it seems I have to override the AuthenticationProvider related authenticate method.
According to use case, I have to implement the Spring Security Pre-authentication,
but the issue is PreAuthenticatedAuthenticationProvider related 'authenticate' method only having the Authentication parameter.
PreAuthenticatedAuthenticationProvider
public class PreAuthenticatedAuthenticationProvider implements
AuthenticationProvider, InitializingBean, Ordered {
public Authentication authenticate(Authentication authentication) {}
}
As solution, is there any possibility to use custom implementation of AuthenticationFailureHandler ?
Thanks.
I have got resolved the issue as following manner,
Implementing a custom AbstractPreAuthenticatedProcessingFilter
Override the doFilter method
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// Get current Authentication object from SecurityContext
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// Call for third party WS when the Authenticator object is null
if (auth == null) {
logger.debug("doFilter : Proceed the authentication");
String appId = "My_APP_ID";
String redirectURL = request.getRequestURL().toString();
// Call for third party WS for get authenticate
if (WS_Authenticator.isAuthenticated(appId, redirectURL)) {
// Successfully authenticated
logger.debug("doFilter : WS authentication success");
// Get authenticated username
String userName = WS_Authenticator.getUserName();
// Put that username to request
request.setAttribute("userName", userName);
} else {
String redirectURL = WS_Authenticator.getAuthorizedURL();
logger.debug("doFilter : WS authentication failed");
logger.debug("doFilter : WS redirect URL : " + redirectURL);
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
((HttpServletResponse) response).sendRedirect(redirectURL);
// Return for bypass the filter chain
return;
}
} else {
logger.debug("doFilter : Already authenticated");
}
} catch (Exception e) {
logger.error("doFilter: " + e.getMessage());
}
super.doFilter(request, response, chain);
return;
}
Override the getPreAuthenticatedCredentials method
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// Get authenticated username
String[] credentials = new String[1];
credentials[0] = (String) request.getAttribute("userName");
return credentials;
}
Implementing a CustomAuthenticationUserDetailsServiceImpl
Override the loadUserDetails method
public class CustomAuthenticationUserDetailsServiceImpl implements AuthenticationUserDetailsService<Authentication> {
protected static final Logger logger = Logger.getLogger(CustomAuthenticationUserDetailsServiceImpl.class);
#Autowired
private UserDataService userDataService;
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
// Get authenticated username
String[] credentials = (String[]) token.getCredentials();
String userName = credentials[0];
try {
// Get user by username
User user = userDataService.getDetailsByUserName(userName);
// Get authorities username
List<String> roles = userDataService.getRolesByUserName(userName);
user.setCustomerAuthorities(roles);
return user;
} catch (Exception e) {
logger.debug("loadUserDetails: User not found! " + e.getMessage());
return null;
}
}
}

How to allow multiple sign in in Spring security?

I would like to enable multi sign-in for my spring security application i.e. For example, if I have two email addresses, I would want to allow the user to sign in with multiple email addresses and the user can shift from one email account to the other, just as Gmail Multiple Sign-in. How could I do that with Spring security?
There seems to be only one Principal instead of list of principals in the Spring security. Could I achieve it?
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Thanks in advance. Hope you will reply as soon as possible.
To some extent Spring Security supports user switch. It is more like a su under Linux.
Nevertheless, you can reuse some code from SwitchUserFilter to create your own user switch.
Primarily, you need to create
Custom Spring Security UserDetails which holds a list of usernames a switch is allowed to
Custom UserDetailsService which populates your custom UserDetails
Custom UserSwitchFilter based on Spring's SwitchUserFilter
Custom UserDetails and UserDetailsService are just examples here and may differ from your own implementation. The idea is to hold a list of usernames in UserDetails for later processing in custom UserSwitchFilter.
CustomUserDetails:
public class CustomUserDetails extends User {
private final Set<String> linkedAccounts;
public CustomUserDetails(String username, String password, Set<String> linkedAccounts, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.linkedAccounts = linkedAccounts;
}
public Set<String> getLinkedAccounts() {
return linkedAccounts;
}
}
CustomUserDetailsService:
public class CustomUserDetailsService implements UserDetailsService {
private UserDao userDao = ...;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BackendUser user = userDao.findUserByUsername(username);
return new CustomUserDetails(user.getHane(), ......);
}
}
Main differences to Spring Security UserSwitchFilter:
Added method checkSwitchAllowed checks if a switch to that specific user from current authenticated user is allowed
switch is based on query paramater and not url for better user experience (see requiresSwitchUser). hence no need for switchUserUrl and targetUrl
Custom UserSwitchFilter ha no notion of a exitUserUrl. Hence no need for exitUserUrl
createSwitchUserToken doesn't modify user authorities
CustomSwitchUserFilter:
public class CustomSwitchUserFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public static final String SPRING_SECURITY_SWITCH_USERNAME_KEY = "j_switch_username";
private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private String switchFailureUrl;
private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationFailureHandler failureHandler;
#Override
public void afterPropertiesSet() {
Assert.notNull(userDetailsService, "userDetailsService must be specified");
if (failureHandler == null) {
failureHandler = switchFailureUrl == null ? new SimpleUrlAuthenticationFailureHandler() :
new SimpleUrlAuthenticationFailureHandler(switchFailureUrl);
} else {
Assert.isNull(switchFailureUrl, "You cannot set both a switchFailureUrl and a failureHandler");
}
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// check for switch or exit request
if (requiresSwitchUser(request)) {
// if set, attempt switch and store original
try {
Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
} catch (AuthenticationException e) {
logger.debug("Switch User failed", e);
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
chain.doFilter(request, response);
}
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
if (username == null) {
username = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Attempt to switch to user [" + username + "]");
}
UserDetails targetUser = userDetailsService.loadUserByUsername(username);
userDetailsChecker.check(targetUser);
checkSwitchAllowed(targetUser);
// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);
if (logger.isDebugEnabled()) {
logger.debug("Switch User Token [" + targetUserRequest + "]");
}
// publish event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(SecurityContextHolder.getContext().getAuthentication(), targetUser));
}
return targetUserRequest;
}
private void checkSwitchAllowed(UserDetails targetUser) {
CustomUserDetails details = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String targetUsername = targetUser.getUsername();
//target username has to be in linked accounts otherwise this is an unauthorized switch
if(!details.getLinkedAccounts().contains(targetUsername)) {
throw new InsufficientAuthenticationException("user switch not allowed");
}
}
private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request, UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
// get the original authorities
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();
// add the new switch user authority
List<GrantedAuthority> newAuths = new ArrayList<GrantedAuthority>(orig);
// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), newAuths);
// set details
targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return targetUserRequest;
}
protected boolean requiresSwitchUser(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
return parameterMap.containsKey(usernameParameter);
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
throws BeansException {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setSwitchFailureUrl(String switchFailureUrl) {
Assert.isTrue(StringUtils.hasText(usernameParameter) && UrlUtils.isValidRedirectUrl(switchFailureUrl),
"switchFailureUrl cannot be empty and must be a valid redirect URL");
this.switchFailureUrl = switchFailureUrl;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
this.userDetailsChecker = userDetailsChecker;
}
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
}
Add CustomSwitchUserFilter to your security filter chain. It has to be placed after FILTER_SECURITY_INTERCEPTOR.
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userDetailsService"/>
</security:authentication-manager>
<security:http use-expressions="true">
<security:intercept-url pattern="/**" access="isFullyAuthenticated()" />
<security:form-login login-page="/login.do" />
<security:logout logout-success-url="/login.do" />
<security:custom-filter ref="switchUserProcessingFilter" after="FILTER_SECURITY_INTERCEPTOR" />
</security:http>
<bean id="switchUserProcessingFilter" class="security.CustomSwitchUserFilter">
<property name="userDetailsService" ref="userDetailsService" />
</bean>
You can find a working example here.

Categories