We are migrating from Spring Security 2.0 to 3.1.
In Spring Security 2.0 the concurrency control was configured adding the following line in the configuration file:
<concurrent-session-control max-sessions="1" />
Now, with 3.1, we added the following:
<session-management invalid-session-url="/index.jsf">
<concurrency-control max-sessions="1" />
</session-management>
We make the login from a backing bean. The code is the following:
public String login(){
Authentication authenticationResponseToken = authenticate();
if (authenticationResponseToken != null && authenticationResponseToken.isAuthenticated()) {
return NavigationConstants.PORTAL_REDIRECT_USER;
}
else{
return NavigationConstants.PORTAL_LOGIN + sessionManagedBean.getUrlQuery();
}
}
private Authentication authenticate(){
debug("authenticate");
AuthenticationManager authenticationManager = (AuthenticationManager) UtilidadesFaces.getSpringBean("authManager");
//simple token holder
Authentication authenticationRequestToken = createAuthenticationToken(sessionManagedBean);
Authentication authenticationResponseToken = null;
//authentication action
try {
authenticationResponseToken = authenticationManager.authenticate(authenticationRequestToken);
SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);
List<GrantedAuthority> authorities = (List<GrantedAuthority>) authenticationResponseToken.getAuthorities();
if(authorities.size() > 0){
this.sessionManagedBean.setRole(authorities.get(0).getAuthority());
}
CustomUser customUser = (CustomUser) authenticationResponseToken.getPrincipal();
this.sessionManagedBean.setIdCl(customUser.getIdCl());
} catch (BadCredentialsException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (AuthenticationServiceException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (LockedException lockedException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.lockedException", this.sessionManagedBean.getActualLanguage());
} catch (DisabledException disabledException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.disabledException", this.sessionManagedBean.getActualLanguage());
}
return authenticationResponseToken;
}
private Authentication createAuthenticationToken(SessionManagedBean sessionManagedBean) {
String username = sessionManagedBean.getUsername() + sessionManagedBean.getIdGuest();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username, sessionManagedBean.getPassword());
return usernamePasswordAuthenticationToken;
}
We already have implemented the equals() and hashCode() methods in the class which implements UserDetails.
No concurrency control is working now, one user can login several times and work with all the sessions simoultaneously.
Any help would be appreciated.
I am using spring 3.2.1 , i just did the following
// spring will inject ConcurrentSessionControlAuthenticationStrategy by default
#Autowired
SessionAuthenticationStrategy sessionAuthenticationStrategy;
and inside your authentication method i just called
sessionAuthenticationStrategy.onAuthentication(authenticationResponseToken, httpReq, httpResp);
And in security.xml file
<session-management invalid-session-url="/login.jsp?time=1">
<concurrency-control error-if-maximum-exceeded="false" max-sessions="1" expired-url="/logout"/>
</session-management>
As all these are default values i think we dont need to set it.
This did the work for me.
Try this..according to me its work
<session-management session-fixation-protection="newSession">
<concurrency-control error-if-maximum-exceeded="true" max-sessions="1" expired-url="/loginexpired" />
</session-management>
Finally i solved the issue calling SessionAuthenticationStrategy#onAuthentication and adding some spring security filters.
My code:
<http auto-config="false" use-expressions="true">
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/index.jsf" />
......
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
</http>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
</beans:bean>
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/index.jsf" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
....
and adding sessionAuthenticationStrategy.onAuthentication()...
private Authentication authenticate(){
debug("authenticate");
AuthenticationManager authenticationManager = (AuthenticationManager) UtilidadesFaces.getSpringBean("authManager");
//simple token holder
Authentication authenticationRequestToken = createAuthenticationToken(sessionManagedBean);
Authentication authenticationResponseToken = null;
//authentication action
try {
authenticationResponseToken = authenticationManager.authenticate(authenticationRequestToken);
SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);
List<GrantedAuthority> authorities = (List<GrantedAuthority>) authenticationResponseToken.getAuthorities();
if(authorities.size() > 0){
this.sessionManagedBean.setRole(authorities.get(0).getAuthority());
}
CustomUser customUser = (CustomUser) authenticationResponseToken.getPrincipal();
this.sessionManagedBean.setIdCl(customUser.getIdCl());
HttpServletRequest httpReq = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpServletResponse httpResp = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
SessionAuthenticationStrategy sessionAuthenticationStrategy = (SessionAuthenticationStrategy) UtilidadesFaces.getSpringBean("sas");;
sessionAuthenticationStrategy.onAuthentication(authenticationResponseToken, httpReq, httpResp);
} catch (BadCredentialsException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (AuthenticationServiceException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (LockedException lockedException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.lockedException", this.sessionManagedBean.getActualLanguage());
} catch (DisabledException disabledException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.disabledException", this.sessionManagedBean.getActualLanguage());
}
return authenticationResponseToken;
}
Related
I am using jersey and spring-oauth2 with spring security. My app is working fine with end points "/oauth/token".
I want to change the endpoints to accept more data. The requirement is, I want to send more details to the token API (i.e. the device details OS, phone/tablet/web etc.). So, I want to override the endpoint and if authentication is successful, I want to store that extra information in database.
I could not find anything related to changing the API in such a way.
Can someone help?
I have found a solution by writing a wrapper controller and assigning default tokenEndpoint bean
#FrameworkEndpoint
public class LoginContrller{
private static Logger logger = org.slf4j.LoggerFactory.getLogger(LoginContrller.class);
private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator();
#Autowired
private UserManager userManager;
#Autowired
TokenEndpoint tokenEndPoint;
#RequestMapping(value = "/user/login", method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters,#RequestBody(required=false) LoginModel loginModel) throws HttpRequestMethodNotSupportedException {
ResponseEntity<OAuth2AccessToken> response = tokenEndPoint.postAccessToken(principal, parameters);
if(!isRefreshTokenRequest(parameters)){
if(loginModel!=null){
loginModel.setUsername(parameters.get("username"));
try {
userManager.loginUser(loginModel);
} catch (UserNotFoundException e) {
logger.warn("Exception in custom login {} ",e);
}
}
}
return response;
}
private boolean isRefreshTokenRequest(Map<String, String> parameters) {
return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}
private boolean isAuthCodeRequest(Map<String, String> parameters) {
return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public void handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
throw e;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
#ExceptionHandler(ClientRegistrationException.class)
public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(new BadClientCredentialsException());
}
#ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
private WebResponseExceptionTranslator getExceptionTranslator() {
return providerExceptionHandler;
}
}
Change in web.xml : just replace the URL with new one
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/user/login</url-pattern>
</servlet-mapping>
And finally create bean with logincontroller class and change the URL in spring-security.xml.
Change the oauth token url and url of clientCredentialsTokenEndpointFilter as mentioned below.
<sec:http pattern="/user/login" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" use-expressions="true" >
<sec:intercept-url pattern="/user/login" access="isFullyAuthenticated()"/>
<sec:csrf disabled="true"/>
<sec:anonymous enabled="false" />
<sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<sec:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER" />
</sec:http>
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<constructor-arg value="/user/login"></constructor-arg>
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/user/login" />
</bean>
<bean class="com.oauth2.provider.endpoint.LoginContrller" />
I have a pretty standard implementation of spring security saml into my application in addition to other authentication mechanisms. Out of the box SAML will not be configured but can be configured through a form, so by default SAML should be disabled. I'd like to easily be able to toggle SAML on / off but am not sure what the best way to do this would be.
It seems like one approach would be to do a custom FilterChainProxy where if I check if saml is enabled and if so to ignore the samlFilter chain(How to delete one filter from default filter stack in Spring Security?) and also do a similar implementation for the Metadata Generator Filter.
Any advice would be great.
Here is my config:
<http auto-config="false" use-expressions="true"
access-decision-manager-ref="webAccessDecisionManager"
disable-url-rewriting="false"
create-session="never"
authentication-manager-ref="authenticationManager">
<custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
<custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</http>
Metadata Generator Filter:
<beans:bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<beans:property name="entityId" value="${saml.entityId}"/>
<beans:property name="signMetadata" value="${saml.signMetadata}"/>
</beans:bean>
</beans:constructor-arg>
</beans:bean>
Saml Filter:
<beans:bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map request-matcher="ant">
<filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
<filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
<filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
<filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
<filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
<filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
<filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
</filter-chain-map>
</beans:bean>
EDIT: Here is my implementation, it is a bit hackish and relies on a deprecated method but it works
The below snippet disables MetadataGeneratorFilter:
public class MyMetadataGeneratorFilter extends MetadataGeneratorFilter {
private boolean isActive = false;
public MyMetadataGeneratorFilter(MetadataGenerator generator) {
super(generator);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (isActive) {
processMetadataInitialization((HttpServletRequest) request);
}
chain.doFilter(request, response);
}
public void setActive(boolean active) {
isActive = active;
}
}
There is also the samlFilter / FilterChainMap which is autowired. If saml is enabled, I leave this chain as is, if it is disabled, I set the chain to an empty map in my service which enables / disables saml.
Upon initialization, I get the filterchainmap values:
private Map<RequestMatcher, List<Filter>> map;
#Override
public void init() throws ServiceException, MetadataProviderException {
SamlConfig samlConfig = getConfig();
map = samlFilter.getFilterChainMap();
applySamlConfig(samlConfig);
}
In the below method, I set the filter chain map to either the original map provided in the spring xml(if enabled) or an empty map (if disabled).
public void applySamlConfig(SamlConfig samlConfig) throws ServiceException, MetadataProviderException {
if (!samlConfig.isEnabled()) {
Map<RequestMatcher, List<Filter>> emptyMap = samlFilter.getFilterChainMap();
emptyMap.clear();
samlFilter.setFilterChainMap(emptyMap);
return;
}
samlFilter.setFilterChainMap(map);
i added a custom filter in the entry-point-ref definition. This filter skips all following filters if the feature is not enabled.
<security:http entry-point-ref="samlEntryPoint">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<!-- This filter checks if the SSO-Feature is enabled - otherwise all following security filters will be skipped -->
<security:custom-filter before="BASIC_AUTH_FILTER" ref="ssoEnabledFilter"/>
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
The ssoEnabledFilter:
public class SsoEnabledFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
boolean ssoEnabled = isSsoEnabled();
if (ssoEnabled) {
filterChain.doFilter(request, response);
} else {
request.getRequestDispatcher(((HttpServletRequest) request).getServletPath()).forward(request, response);
}
}
}
So far I've been implementing this using a custom Spring namespace which includes or skips certain beans based on the backend configuration and reloading of the Spring context in case the backend configuration changes.
Edit : fixed error signaled by TheTurkish
If you want to be able to switch the use of SAML on a running application, the simpler would be to use a wrapper around samlFilter. For example
public class FilterWrapper extends GenericFilterBean {
private Filter inner;
private boolean active;
private boolean targetFilterLifeCycle = false;
public Filter getInner() {
return inner;
}
public void setInner(Filter inner) {
this.inner = inner;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
#Override
public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
if (active) {
inner.doFilter(sr, sr1, fc);
}
else {
fc.doFilter(str,sr1);
}
}
#Override
protected void initFilterBean() throws ServletException {
super.initFilterBean();
if (inner == null) {
throw new ServletException("Inner cannot be null");
}
if (targetFilterLifeCycle) {
inner.init(getFilterConfig());
}
}
#Override
public void destroy() {
super.destroy();
if (inner != null && targetFilterLifeCycle) {
inner.destroy();
}
}
}
You can use it that way :
<bean id="samlFilter" class="...FilterWrapper" p:active="false">
<property name=inner>
<!-- the real samlFilter bean -->
<bean class="org.springframework.security.web.FilterChainProxy">
...
</bean>
</property>
</bean>
As it is a bean, you inject it where you want to activate/deactivate Saml and simple call :
samlFilter.setActive(active);
I have an application with spring security 3.1 and Ldap integration. Below are the key points in the requirement and implementation so far:
The application will have multiple roles for single user but these
roles does not exist in ldap, so the application authenticates only
the username(or userid) from ldap.
The roles are stored separately in the database
Upon successful authentication from ldap, the userdetails and the roles are set into principal object custom userdetails object by implementing UserDetailsService
Problem:
User A logs in the application
User B logs in the application, User A session is getting destroyed(which should not have happened because User A has not logged out yet!)
User B logs out User A gets page not found, since its session is already destroyed when User B logged in.
The applicationContext-security.xml looks like this:
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.jsp" />
<beans:property name="forceHttps" value="true" />
</beans:bean>
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/login.jsp?login_error=2" />
</beans:bean>
<beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<beans:constructor-arg value="/login.jsp" />
<beans:constructor-arg>
<beans:list>
<beans:ref bean="logoutEventBroadcaster" />
<beans:bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
</beans:list>
</beans:constructor-arg>
<beans:property name="filterProcessesUrl" value="/j_spring_security_logout" />
</beans:bean>
<beans:bean id="myAuthFilter" class="com.*.security.CustomAuthenticationProcessingFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="adAuthenticationProvider" />
</authentication-manager>
<beans:bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="*.*.net" />
<beans:constructor-arg value="ldap://*.*.net:389/" />
<beans:property name="userDetailsContextMapper">
<beans:bean class="com.ezadvice.service.CustomUserDetailsContextMapper" />
</beans:property>
<beans:property name="useAuthenticationRequestCredentials" value="true" />
</beans:bean>
<beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login.jsp?login_error=1" />
</beans:bean>
<beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/home.do" />
<beans:property name="alwaysUseDefaultTargetUrl" value="true"/>
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
<beans:property name="migrateSessionAttributes" value="false" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
The CustomAuthenticationProcessingFilter class looks like this:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String roleId = request.getParameter("roleId");
String username = request.getParameter("j_username");
TbEzaLoginHistory tbEzaLoginHistory = null;
// check if the user has authority for the role
TbEzaUser tbEzaUser = userManagementService.checkUserAndRole(roleId, username);
if (null != tbEzaUser) {
tbEzaLoginHistory = userManagementService.saveLoginHistory(tbEzaUser, roleId);
request.setAttribute("loginHistoryId", tbEzaLoginHistory.getLoginKey());
request.setAttribute("roleId", roleId);
request.setAttribute("j_username", username);
if (UserTracker.increment(username, roleId)) {
try{
Authentication attemptAuthentication = super.attemptAuthentication(request, response);
if (null != attemptAuthentication) {
CustomUser principal = (CustomUser) attemptAuthentication.getPrincipal();
if (null == principal && null != tbEzaLoginHistory)
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
return attemptAuthentication;
}
}
catch (CommunicationException e) {
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
UserTracker.decrement(username, roleId);
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=5");
try {
dispatcher.forward(request, response);
} catch (ServletException se) {
se.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
LOGGER.debug("Connection Timeout error for UserName:"+username +"\n" + e);
e.printStackTrace();
}
}else {
if (null != tbEzaLoginHistory)
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=4");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=3");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(EXITLOGGER + " attemptAuthentication");
}
return null;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, authResult);
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authResult;
WebAuthenticationDetails details = (WebAuthenticationDetails) token.getDetails();
String address = details.getRemoteAddress();
CustomUser user = (CustomUser) authResult.getPrincipal();
String userName = user.getUsername();
System.out.println("Successful login from remote address: " + address + " by username: "+ userName);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ENTRYLOGGER + " unsuccessfulAuthentication");
}
try {
Long loginHistoryId = (Long) request.getAttribute("loginHistoryId");
String username = (String) request.getAttribute("j_username");
String roleId = (String) request.getAttribute("roleId");
userManagementService.deleteFromLoginHistory(loginHistoryId);
super.unsuccessfulAuthentication(request, response, failed);
UserTracker.decrement(username, roleId);
} catch (Exception e) {
e.printStackTrace();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(EXITLOGGER + " unsuccessfulAuthentication");
}
}
The UserTracker class looks like this:
public class UserTracker {
private static Set<String> loggedInUsersDetails = new HashSet<String>();
#SuppressWarnings("unchecked")
synchronized public static boolean increment(String userName, String roleId) {
if(loggedInUsersDetails.add(userName.toLowerCase()+'~'+roleId)){
return true;
}else
return false;
}
synchronized public static void decrement(String userName, String roleId) {
loggedInUsersDetails.remove(userName.toLowerCase()+'~'+roleId);
}
Can anyone help me to find out, why the User A's session is getting destroyed ?
In the docs (SavedRequests and the RequestCache Interface), they talk about ExceptionTranslationFilter job to cache the current request before invoking the AuthenticationEntryPoint. This allows the request to be restored - by the SavedRequestAwareAuthenticationSuccessHandler (which is the default).
But I've noted another evel filter: RequestCacheAwareFilter.
AFTER the redirection to the origional request, the RequestCacheAwareFilter is invoked by the chain, and he calls 'getMatchingRequest()', that gets the request, and then removes it from the cache! then, when the second authentication succeeds (from the 2nd user), there is no URL in the cache, so Spring does not know where to redirect me to. so I believe this is the root-cause of the problem.
I've found out that this issue was born due to this jira:
SEC-1241: SavedRequest not destroyed after successful authentication
You can move your authentication code into custom AuthenticationManager. AuthenticationManager will have two dependencies on LdapAuthenticationProvider and DaoAuthenticationProvider. During authentication processing it will be responsible for:
calling LDAP provider
calling DB provider
combining two authentication objects into one (credentials from LDAP and roles from DB).
Finally found the solution to the above problem. There were multiple causes:
While testing the above problem I was making a mistake, that I was trying to achieve concurrency control when users opens the application in a tabbed browser.
Spring internally stores the ip address of the machine to prevent multiple users to login from same machine. Thus had to make code changes so that user's having multiple roles are not allowed to login from the same machine.
Remove
<beans:property name="maximumSessions" value="1" />
at
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
<beans:property name="migrateSessionAttributes" value="false" />
</beans:bean>
The customer want to have the following scenario:
Customer hands out link (webapp address) with 2 parameters to the webapp user. Based on these variables the user will take on specific roles in the webapp.
I don't want any authorization in it. There should only be the authentication check which looks at these url parameters and checks if they are valid and will connect the user to the appropriate role.
How can I realize this?! Is there already a solution available?
Thanks!
regards Matthias
I already solved the problem.
For those who are interested ....
web.xml
<!-- ===== SPRING CONFIG ===== -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>
applicationContext.xml
<context:component-scan base-package="at.beko.rainstar2" />
<tx:annotation-driven transaction-manager="transactionManager" />
applicationContext-security.xml
<!-- Configuring security not finished!! -->
<http create-session="never" use-expressions="true" auto-config="false"
entry-point-ref="preAuthenticatedProcessingFilterEntryPoint">
<intercept-url pattern="/authError.xhtml" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<custom-filter position="PRE_AUTH_FILTER" ref="preAuthFilter" />
<session-management session-fixation-protection="none" />
</http>
<beans:bean id="userDetailsServiceImpl"
class="at.beko.rainstar2.service.impl.UserDetailsServiceImpl" />
<beans:bean id="preAuthenticatedProcessingFilterEntryPoint"
class="at.beko.rainstar2.model.LinkForbiddenEntryPoint" />
<beans:bean id="preAuthenticationProvider"
class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService"
ref="userDetailsServiceImpl" />
</beans:bean>
<beans:bean id="preAuthFilter"
class="at.beko.rainstar2.service.filter.UrlParametersAuthenticationFilter">
<beans:property name="authenticationManager" ref="appControlAuthenticationManager" />
</beans:bean>
<authentication-manager alias="appControlAuthenticationManager">
<authentication-provider ref="preAuthenticationProvider" />
</authentication-manager>
LinkForbiddenEntryPoint.java
public class LinkForbiddenEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendRedirect("/rainstar2-webapp/authError.xhtml");
}
}
UrlParametersAuthenticationFilter.java
public class UrlParametersAuthenticationFilter extends
AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (request.getParameterMap().size() == 2) {
return true;
}
return false;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
String[] credentials = new String[2];
credentials[0] = request.getParameter("param1");
credentials[1] = request.getParameter("param2");
return credentials;
}
}
UserDetailsServiceImpl.java
#SuppressWarnings("deprecation")
public class UserDetailsServiceImpl implements
AuthenticationUserDetailsService<Authentication> {
#Override
public UserDetails loadUserDetails(Authentication token)
throws UsernameNotFoundException {
UserDetails userDetails = null;
String[] credentials = (String[]) token.getPrincipal();
boolean principal = Boolean.valueOf(token.getCredentials().toString());
if (credentials != null && principal == true) {
String name = credentials[0];
if ("admin".equalsIgnoreCase(name)) {
userDetails = getAdminUser(name);
} else if ("händler".equalsIgnoreCase(name)) {
userDetails = getRetailerUser(name);
} else if ("user".equalsIgnoreCase(name)) {
userDetails = getUserUser(name);
}
}
if (userDetails == null) {
throw new UsernameNotFoundException("Could not load user : "
+ token.getName());
}
return userDetails;
}
private UserDetails getAdminUser(String username) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_USER"));
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_RETAILER"));
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
return new User(username, "notused", true, true, true, true,
grantedAuthorities);
}
private UserDetails getRetailerUser(String username) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_USER"));
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_RETAILER"));
return new User(username, "notused", true, true, true, true,
grantedAuthorities);
}
private UserDetails getUserUser(String username) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_USER"));
return new User(username, "notused", true, true, true, true,
grantedAuthorities);
}
}
The way I have resolved this with similar situations is to to use a servlet filter to grab the parameters. I would recommend extending the org.springframework.web.filter.GenericFilterBean.
From these parameters, create an auth object of some sort (such as a token), that can be passed into the AuthenticationManager which you can autowire in (or get in some other method).
You will then need to have an AuthenticationProvider that can handle your auth object and generate a UserDetails object with the GrantedAuthority collection you need to satisfy the specific roles you want the user to have.
I am new to Spring and my requirement is that I do not want to authenticate the user with username and password.
The user is authenticate is some other application and my app get the request with folloing details:
User name
Roles
I just want use Spring Security to secure the pages according to the roles in the request.
I've given a thought about writing UserDetailService, but that only add request-data, Spring still ask for authentication information.
Then I thought about writing something like the following:
public class UserLogin {
/*
#Resource(name = "userDetailsService")
private UserDetailsService userDetailsService;
*/
#Resource(name = "authenticationManager")
private AuthenticationManager authenticationManager;
public boolean login(UserEntity user) {
//UserDetails ud = userDetailsService.loadUserByUsername(username);
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : user.getAuthorities()) {
authorities.add(new GrantedAuthorityImpl(role));
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), authorities);
try {
Authentication auth = authenticationManager.authenticate(token);
SecurityContext securityContext = new SecurityContextImpl();
// Places in ThredLocal for future retrieval
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (AuthenticationException e) {
return false;
}
return true;
}
}
Am I going in the right direction. If so, how to configure the whole thing .. in spring-xml .
You're in what's called a Pre-Authentication scenario, where you configure Spring Security to only Authorize access, not Authenticate access. See http://static.springsource.org/spring-security/site/docs/3.0.x/reference/preauth.html. Here is a full configuration, where you need to implement AbstractPreAuthenticatedProcessingFilter to grep your authentication scheme's UserPrincipal, and the custom UserDetailsService you mention above.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns:security="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<security:global-method-security secured-annotations="enabled" />
<beans:bean id="preAuthenticatedProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
<security:http auto-config="false" entry-point-ref="preAuthenticatedProcessingFilterEntryPoint">
<security:custom-filter position="PRE_AUTH_FILTER" ref="myCustomPreAuthFilter" />
</security:http>
<beans:bean id="myCustomPreAuthFilter" class="com.mypackage.MyCustomPreAuthFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
<beans:bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService">
<beans:bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<beans:property name="userDetailsService" ref="myCustomUserDetailsService"/>
</beans:bean>
</beans:property>
</beans:bean>