In my web-application I'm using Spring Security and UserDetailsService implementation for authentication.
Now I neet to get and store somewhere the client IP address for the current session and I'd like to store it in UserDetails instance to retrieve it where I need.
Which is the correct way to achieve this? Is there any facility in Spring MVC/Security to get IP address in service layer?
NOTE I also need to know IP address if the client is not authenticated (to log access attempt)
The ip-address is already present in the Authentication object (not the UserDetails).
You can call getDetails() on the Authentication object and in a web application and properly configured Spring Security environment this will give you an instance of WebAuthenticationDetails which has the ip-address inside it. You can call the getRemoteAddress method to obtain the address. (See the javadoc)..
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
String ipAddress = details.getRemoteAddress();
Something along those lines, you could put this behind a utility method or something to obtain the ip-address.
Apparently you want to log authentication attempts, this can be easily achieved by implementing an ApplicationListener and let that listen to AbstractAuthenticationEvents. Spring Security issues those for each authentication attempt, and also includes the Authentication (containing the IP-address) into it.
public class AuthenticationAttemptLoggerListener implements ApplicationListener<AbstractAuthenticationEvent> {
private final Logger logger = LoggerFactory.getLogger(AuthenticationAttemptLoggerListener.class);
public void onApplicationEvent(AbstractAuthenticationEvent event) {
Authentication auth = event.getAuthentication();
WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
String ipAddress = details.getRemoteAddress();
if (event instanceof AbstractAuthenticationFailureEvent) {
logger.warn("Unsuccesful authentication attemped from: {}", ipAddress);
} else {
logger.info("Succesful authentication attemped from: {}", ipAddress);
}
}
}
Something like this should catch and log everything. You might want to take a look at all the available events.
Related
On a local Wildfly server I have a simple Java servlet like below:
import javax.servlet.annotation.HttpMethodConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.http.HttpServlet;
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", rolesAllowed = { "debug" })})
public class DebugServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
try ( PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */
...
Principal user = request.getUserPrincipal();
out.println("<p>" + (user != null ? user.getName() : "No user") + "</p>");
...
}
}
}
I have set up authentication with Wildfly's Elytron v1.17.2 using OpenID Connect (OIDC) with a third party authentication server. If it matters, it is not a Keycloak server, and changing the auth server isn't an option; using this auth server is a strict business requirement. The auth server has a bunch of users which we want to give access to, but for my particular client_id none of the users have any roles associated with them, and we want to avoid adding roles through the auth server because the process is...tedious at best. Bureaucratic red tape, every time, for every user. Our end goal is to have our own roles in this server's database and we're only going to the 3rd party to handle the login and then verify the login is valid and which user it is.
If I include rolesAllowed = { "debug" } in the HttpMethodConstraint, authentication with the 3rd-party auth server gets triggered and works correctly. But because none of the users have roles associated with them, they don't have the required "debug" role. The processRequest method is never reached and the user gets a "Forbidden" error page.
If I remove or empty the rolesAllowed field of the constraint, authentication with the 3rd-party auth server is not triggered, and there's no UserPrincipal which we can use to identify the user.
I've tried putting a javax.servlet.Filter in to intercept the request before it gets to my Servlet, but if the "debug" role is required then the filter doesn't get reached either. I had hoped to intercept the request after the user authenticates and then add roles to the user, or a session variable, or something which my servlet would then check for permission to do various actions.
I've tried a few other combinations which have not triggered authentication:
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", emptyRoleSemantic = ServletSecurity.EmptyRoleSemantic.PERMIT)})
and
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", transportGuarantee = ServletSecurity.TransportGuarantee.CONFIDENTIAL)})
No luck.
Is there a way I can trigger 3rd-party authentication, and get a UserPrincipal with the authenticated user's username/user id, without requiring a specific role? -OR- Is there a way I can add roles to the authenticated user after authenticating but before being blocked by the HttpMethodConstraint?
The reason your custom filter approach will not work is because the elytron security framework handles this well before your filter chain is invoked. You might try custom role mapper within elytron security as explained here (Section 3.2.9) and here to add some predefined roles.
RoleMapper - After the roles have been decoded for an identity further
mapping can be applied, this could be as simple at normalising the
format of the names through to adding or removing specific role names.
You would obviously need to configure the elytron subsystem to use your custom role mapper. You can take a look at an example configuration for regex-role-mapper here ( in your case you need a custom RoleMapper instead of regex-role-mapper)
I believe elytrons role mapping is the way to go. You could configure a Regex mapper to map every role to one static role. Or use the group-decoder-mapper to convert group names into roles.
See for example: https://wildfly-security.github.io/wildfly-elytron/blog/regex-role-mapper/
I am starting with Spring-Boot and have an application with WebSecurity.
Its working fine, I have InMemory Authentication with static user/passwords.
Now I have no need for DB or LDAP or ...
#Override
public void configure (AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication ()
.withUser ("sam").
.password (passwordEncoder ().encode ("secret"))
.authorities ("ROLE_USER");
}
But I want to build a custom authenticator that uses dynamic data (e.g. password has current time in it).
How to implement a custom authenticator? How can I see username and password and implement a check?
Thanks for help!
To provide custom authentication you can refer to this link https://youtu.be/TNt3GHuayXs
and for seeing the username and password to check you can use principal object like this
Object principal= SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Let me know if you need more info
In my application I am trying to unite ActiveDirectory authentication with OAuth2 refresh tokens.
I was able to successfully authenticate via ActiveDirectoryLdapAuthenticationProvider. I have also provided my custom implementation of LdapUserDetailsMapper that populates the UserDetails with some custom attributes taken from ActiveDirectory. Key thing here is that these attributes have a confidentialty flag set on them and are only available to the user itself (i.e. authenticated user could read the values of these attributes for himself but not for the others). These attributes are stored in Authentication object and are used by an application in a context of an authenticated user.
Things get tricky when I try to add refresh tokens to the picture. Refresh tokens require me to implement a UserDetailsService where I have to provide new UserDetails having just a user name. This is not feasible due to confidentialty flag. Even if I have some master account in my application with the ability to browse ActiveDirectory I will not be able to retrieve the confidential attributes.
So I would rather prefer to provide more atomic implementations like the function that checks if the user is still active or the function that provides a renewed set of user authorities. Unfortunately I did not find this level of atomicity in Spring Security. So it looks like for refresh tokens I have to provide an implementation of UserDetailsService.
If I have to provide new user details I would like to have an access to previous user Authentication object. In this case I will check the user and if it is still active I will copy all the confidential information from previous Authentication. The problem is that it does not seem to be available. At the moment when UserDetailsService::loadUserByUsername() is called SecurityContextHolder.getContext() does not contain the user authentication. Authentication is also not available from UserDetailsService API - I only get the user name. At the same time user's Authentication object is present just one stack frame up in UserDetailsByNameServiceWrapper class:
public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {
return this.userDetailsService.loadUserByUsername(authentication.getName());
}
The least thing I want to do here is to implement some in-memory storage for all user confidential information to be used whenever I need to provide new UserDetails. I already have all the required information in user authentication managed by Spring and doing this on my end seems to be just surplus.
And here comes question list:
If you feel that I am doing something terribly wrong from the perspective of application security architecture, please tell me
Is there a way to tell Spring during refresh token procedure to use previous UserDetails object so that application could just answer the question if the user is still active and should be issued a new access token (and not provide the UserDetailsService at all)?
Is there a way to get previous user Authentication object during the call to UserDetailsService::loadUserByUsername() so that I could use it as a source of confidential info?
Is there some other approach that I do not see at the moment to add refresh tokens to my application?
Update:
Here I saw a comment that you could implement your own AuthenticationUserDetailsService to work around the problem. This I do not see how to do. It is hardcoded in AuthorizationServerEndpointsConfigurer that it always creates an instance of UserDetailsByNameServiceWrapper so to provide your own implementation you would have to interfere into AuthorizationServerEndpointsConfigurer initialization process.
OK, looks like the answer with Spring Security 4.0 is you can't.
So I had to apply the following hack which works, but I do not like it very much. Since it works I am posting it here. Since it does not solve the original problem, but works around it I will not mark it as accepted by the author.
Switch to JWT tokens
Use custom TokenEnhancer to inject all information that is required to recreate the user (user secret in my case) to the token directly. Of course, the value must be encrypted by the server with symmetrical crypto algorithm before adding it to the token.
Instruct authorization server to use custom AccessTokenConverter. This implementation of AccessTokenConverter would extract the secret value from the token, decrypt it and put it to ThreadLocal field.
Instruct custom UserDetailsService to retrieve the user secret from the ThreadLocal field set in step 3. This is the best way I found so far to deliver the current authorization context to UserDetailsService. And this is the part that I do not like most in my solution.
Use custom security filter to erase the value set in step 3 from ThreadLocal field.
P.S. I still do not see the possibility to implement custom AuthenticationUserDetailsService that was mentioned earlier. If such possibility exists it could have been another way to solve the problem.
Some useful links:
Extending Spring Security OAuth for Multi-Tenant
Detailed explanation of the problem in spring-security-oauth GitHub
I've got the response from Joe Grandja on spring-security-oauth github page.
Posting it here since it actually provides an answer to the original question.
Hi #masm22. To help with question 1 and 2, below is a custom configuration that will allow you to hook into the refresh_token grant and provide your own behaviour or delegate to super to proceed with current behaviour. It will also allow you to access the user Authentication so you can read your custom (confidential) attributes.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
..... // other config
#Autowired
private ClientDetailsService clientDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(this.customTokenServices());
}
private DefaultTokenServices customTokenServices() {
DefaultTokenServices tokenServices = new CustomTokenServices();
tokenServices.setTokenStore(new InMemoryTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(this.clientDetailsService);
return tokenServices;
}
private static class CustomTokenServices extends DefaultTokenServices {
private TokenStore tokenStore;
#Override
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
OAuth2RefreshToken refreshToken = this.tokenStore.readRefreshToken(refreshTokenValue);
OAuth2Authentication authentication = this.tokenStore.readAuthenticationForRefreshToken(refreshToken);
// Check attributes in the authentication and
// decide whether to grant the refresh token
boolean allowRefresh = true;
if (!allowRefresh) {
// throw UnauthorizedClientException or something similar
}
return super.refreshAccessToken(refreshTokenValue, tokenRequest);
}
#Override
public void setTokenStore(TokenStore tokenStore) {
super.setTokenStore(tokenStore);
this.tokenStore = tokenStore;
}
}
}
The other thing I want to point out for your information is in DefaultTokenServices.refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
has the following code:
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// The client has already been authenticated, but the user authentication might be old now, so give it a
// chance to re-authenticate.
Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
user = authenticationManager.authenticate(user);
Object details = authentication.getDetails();
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
}
The user is being re-authenticated. Possibly something you may want to do in your custom implementation if need be.
We are using shiro in our application, and the session are saved in the database for scale. And we have our own accounts databse, so far so good.
This is the core security components:
DatabaseRealm
Vvalidate user by UsernameAndPasswordToken and the password in the database, retrieve the permissions fro the database.
DatabaseSessionDao
Extends the CachingSessionDAO, for create,read,delete sessions from the database.
DefaultWebSessionManager
Shiro built in components.
Now we have to make two kinds of improvement:
Integrate the OAuth login
For example, user should be able to login by Google or Facebook or their own accounts registered in our application.
Then I wonder how can we re-use the existed security components like the DatabaseRealm, since the realm will check the AuthenticationInfo's credentials which is not avaiable in the OAuth context:
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SimpleAuthenticationInfo info = null;
if (token instanceof UsernamePasswordToken) {
UsernamePasswordToken uToken = (UsernamePasswordToken) token;
User user = queryUserByName(uToken.getUsername());
info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword().toCharArray(), getName());
if (user.getSalt() != null) {
info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
}
} else if (token instanceof OAuthUserToken) {
OAuthUserToken oToken = (OAuthUserToken) token;
String type = oToken.getOauthType();
String openId = oToken.getOpenID();
//then what should I do to make the `Credentials` check passed?
}
return info;
}
How to fix this?
Using JWT(Json Web Token)
The sessions are saved to the database for cluster deployment at the moment, however we found that it may slow our response, also we need to provide api for the mobile paltform, so we try to use JWT.
While it seems that shior use cookie + session to identify if user have been authenciated or not. I have no idea how to replace that.
Any suggestions?
It may be better to add new Realm / Filter / Login url for redirection.
GoogleRealm
public class GoogleOAuthRealm extends AuthorizingRealm {
...
public GoogleOAuthRealm() {
//OauthToken implements AuthenticationToken to hold code
setAuthenticationTokenClass(OauthToken.class);
}
...
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OauthToken tk = (OauthToken) token;
String authCode = (String) tk.getPrincipal();
//1. fetch token by posting code to google
//2. validation & parse token
//org.apache.shiro.authz.SimpleAuthorizationInfo
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
//set permission manually
return info;
}
...
}
config GoogleRealm in securityManager, and redirect to a new url after google login success.
public class GoogleAuthenticatingFilter extends FormAuthenticationFilter
<property name="filterChainDefinitions">
<value>
...
/login/googleLogin = GoogleAuthenticatingFilter
...
</value>
</property>
As neuo wrote above, use a different realm for OAuth and then do some wiring up on the back end to reconcile the user credentials. For your scaling and performance, I’d suggest using something like Redis as a session cache - that will save you having to persist sessions in the database.
Re-Using Existing Components
Consider the construct in which you are working, you probably do not want to reuse the DatabaseRealm for other authentication schemes. Rather create individual realms for Google, Facebook, etc.
This way you will be able to control what AuthenticationTokenand AuthenticationInfo will be used in each Realm.
JWT/Cookie Session
From what you decribe I think you would likely want to create a RESTful API for your mobile platform. The REST spec requires that the server does not maintain, and more importantly not rely on server-side state.
Fortunately shiro allows you to configure endpoints that do not create a session when a request is received. More details on session management here
Considering the above, session tracking becomes irrelevant, and you could use the JWT token as a form of Bearer token to tacitly authenticate the user on every request. Be sure to consider the security short-falls of the Bearer token, which may be mitigated by always using an encrypted connection.
I'm using Spring OAuth 2.0, and I want to protect my application against URL redirect attack. Is there a way to validate the redirect URL at the authorization server ?
The authorization server should only perform a redirect if the redirect_uri matches the one registered by the client. So there shouldn't be any need for you to perform a separate check.
If in doubt, try sending an authorization request with a completely different redirect_uri and see what happens.
Should be implemented ClientDetailsService class and configure registered redirect uri's.
#Service
#Transactional
public class CustomClientDetailsService implements ClientDetailsService {
#Override
public ClientDetails loadClientByClientId(String clientId) {
String registeredRedirectUris; // Get registeredRedirectUris
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setRegisteredRedirectUri(registeredRedirectUris);
return clientDetails;
}
}