I would like to get list of all users which are currently logged in my application. I know, that I should inject SessionRegistry in my code to call getAllPrincipals() method. Unfortunatelly, I always get empty list. It seems that SessionRegistry is not populate and I don't know how to make it. I know, that on StackOverflow there are similar questions, but I still have problem with solving my problem. Firstly I add to my web.xml this code:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
As I know it allows to get information about lifecycle (create, destroy) of sessions. And that's all. I have big problem with moving forward. My file with security beans is presented below:
<beans:bean id="successAuth" class="pl.fp.microblog.auth.SuccessAuth"/>
<beans:bean id="failureAuth" class="pl.fp.microblog.auth.FailureAuth"/>
<http auto-config="true">
<intercept-url pattern="/" access="ROLE_USER" />
<intercept-url pattern="/profile/*" access="ROLE_USER" />
<form-login
login-page="/login"
authentication-success-handler-ref="successAuth"
authentication-failure-handler-ref="failureAuth"
username-parameter="username"
password-parameter="password"
login-processing-url="/login_processing_url"
/>
<security:logout logout-url="/logout_processing_url"/>
<session-management>
<concurrency-control max-sessions="1" session-registry-alias="sessionRegistry"/>
</session-management>
</http>
<beans:bean id="saltProvider" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
<beans:property name="userPropertyToUse" value="username"></beans:property>
</beans:bean>
<beans:bean id="userService" class="pl.fp.microblog.auth.UserService">
<beans:property name="userDAO" ref="userDAO"/>
</beans:bean>
<authentication-manager>
<security:authentication-provider user-service-ref="userService">
<password-encoder hash="sha-256">
<security:salt-source ref="saltProvider"/>
</password-encoder>
</security:authentication-provider>
</authentication-manager>
Here I call getAllPrinciples() method:
#Transactional
#Controller
public class SiteController {
#Autowired
private UserDAO userDAO;
#Autowired
private SessionRegistry sessionRegistry;
#RequestMapping(value = "profile/{login}")
public String profilePage(#PathVariable String login, HttpServletRequest req) throws SQLException {
...
sessionRegistry.getAllPrincipals().size()
...
return "profile";
}
}
I tried to add session-managemenent code into my http, ConcurrentSessionFilter and similar things, but in fact I don't understand it. And the documentation is too complex for me. Does anyone could help me and tell step by step what to do next? Which beans should I add?
I think you are almost there. The only thing you've probably missed is the use of session-registry-alias. By using that attribute on the concurrency-control element you expose the session registry, so that it can be injected to your own beans. See the reference doc.
So what you need is:
<http auto-config="true">
...
<session-management>
<concurrency-control max-sessions="1" session-registry-alias="sessionRegistry"/>
</session-management>
</http>
Now you have a reference to the session registry that will be populated by the ConcurrentSessionControlStrategy which is set up implicitly by the above configuration. To use it you would just inject it to your bean as normal:
<bean class="YourOwnSessionRegistryAwareBean">
<property sessionRegistry="sessionRegistry"/>
</bean>
Please note that the above configuration will also restrict the number of concurrent sessions a user may have. If you don't want this restriction, you will have to forgo the convenience of the namespace configuration, because the namespace schema doesn't allow you to set the max-sessions attribute to -1. If you need help on how to wire up the necessary beans manually, the reference doc gives detailed instructions on that.
Related
My requirement is, I have two different landing pages one for user and one for admin. This landing pages has to be appear based on the intercept url pattern which i configure in the spring security xml file. Both the landing page is having a hyperlink to login, when the user click on the login hyperlink of adminLayout.jsp it will load the same login page and when the user click on the login hyperlink of userLayout.jsp it will load the same login page by interacting with the controller for two different url patterns.The url patterns will be /admin and /user.
I stuck here.
How can i configure two different landing pages(adminLayout) and userLayout) in spring security. this tow landing pages is having the same login form, which i want to configure in the spring security form-login for both the url patterns and tow layouts.Before login only the landing page has to appear and then when the user click on the login hyperlink from two different pages it has to make user of spring security provided login-form functionality.Please help me out on this.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" requires-channel="http" />
<intercept-url pattern="/user**" access="hasRole('ROLE_USER')" requires-channel="http" />
<csrf disabled="true"/>
<access-denied-handler ref="accessDeniedHandler"/>
<!-- Here i want to configure the landing pages based on the intercept url pattern if the pattern is /admin i want to dispaly the adminLayout.
If the intercept url pattern is /user i want to display the userLayout. Both this Layout pages is having common login page which user will click from this layout pages.
if want to make use of spring secuiryt for form login..
-->
<form-login login-processing-url="/j_spring_security_check"
login-page="/sslogin" authentication-success-handler-ref="authenticationHandler"
authentication-failure-url="/fail2login?error=true"/>
<logout logout-success-url="/logout" invalidate-session="true" logout-url="/j_spring_security_logout" delete-cookies="JSESSIONID" />
<session-management>
<concurrency-control error-if-maximum-exceeded="true" max-sessions="1" expired-url="/fail2login" />
</session-management>
</http>
<beans:bean id="accessDeniedHandler" class="com.fss.portal.handlers.PortalAccessDeniedHandler">
<beans:property name="accessDeniedURL" value="403"></beans:property>
</beans:bean>
<beans:bean id="authenticationHandler" class="com.fss.portal.handlers.AuthenticationHandler">
</beans:bean>
<beans:bean id="customAuthenticationProvider" class="com.fss.portal.utility.CustomAuthenticationProvider">
<beans:property name="passwordEncoder" ref="bcryptEncoder"></beans:property>
</beans:bean>
<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<authentication-manager>
<authentication-provider ref="customAuthenticationProvider">
</authentication-provider>
</authentication-manager>
I think you should try creating a AuthenticationEntryPoint implementation with multiple landing page support.
It could be something like this:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
public class MultipleLandingPageEntryPoint extends LoginUrlAuthenticationEntryPoint
implements AuthenticationEntryPoint {
private Map<String, String> landingPages;
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl, Map<String, String> landingPages) {
super(defaultLoginFormUrl);
this.landingPages = landingPages;
}
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl) {
super(defaultLoginFormUrl);
}
public Map<String, String> getLandingPages() {
return landingPages;
}
public void setLandingPages(Map<String, String> landingPages) {
this.landingPages = landingPages;
}
#Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
for(String key : this.landingPages.keySet()){
RequestMatcher rm = new RegexRequestMatcher(key, null);
if(rm.matches(request)){
return this.landingPages.get(key);
}
}
// If not found in the map, return the default landing page through superclass
return super.determineUrlToUseForThisRequest(request, response, exception);
}
}
Then, in your security config, you must configure it:
<beans:bean id="authenticationMultiEntryPoint" class="com.xxx.yyy.MultipleLandingPageEntryPoint">
<beans:constructor-arg value="/user/landing.htm" />
<beans:property name="landingPages">
<beans:map>
<beans:entry key="/user**" value="/user/landing.htm" />
<beans:entry key="/admin**" value="/admin/landing.htm" />
</beans:map>
</beans:property>
</beans:bean>
And use it in your <security:http> element:
<security:http pattern="/admin/landing.htm" security="none" />
<security:http pattern="/user/landing.htm" security="none" />
<security:http auto-config="true" use-expressions="true"
entry-point-ref="authenticationMultiEntryPoint">
If you implement the AuthenticationEntryPoint extending LoginUrlAuthenticationEntryPoint (which I think it's a good idea) check additional parameters on it.
EDIT: I've just updated the class implementation, did not include the latest version
Create A Custom AuthenticationSuccessHandler like below
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
Authentication authentication)
throws IOException,
javax.servlet.ServletException {
if(authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN")) {
request.getRequestDispatcher("/admin").forward(request, response);
} else if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER")) {
request.getRequestDispatcher("/user").forward(request, response);
}
}
}
And configure it with form-login tag as following
<bean id="customAuthenticationSuccessHandler" class="CustomAuthenticationSuccessHandler" />
<form-login authentication-success-handler-ref="customAuthenticationSuccessHandler" ...>
UPDATE
Create a Controller mappings /landing point to it by <form-login login-page="/landing" .../>. This landing should have links to admin and user landing pages. Which can have links or forms to login.
You can remove protection from these landing pages.
<http pattern="/landing**" security="none"/>
<http pattern="/landing/admin**" security="none"/>
<http pattern="/landing/user**" security="none"/>
And you can write a Custom AuthenticationFailureHandler to redirect to correct login page.
I have secured certain url patterns for my project based on the users role as seen in my spring_security xml below.
<security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied.do" >
<security:intercept-url pattern="/auth/login" access="permitAll"/>
<security:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/security/**" access="hasRole('ROLE_SECURITY')"/>
<security:intercept-url pattern="/common/**" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/notsecure/**" access="permitAll"/>
<security:form-login
login-page="/auth/login.do"
authentication-failure-url="/auth/login.do?error=true"
default-target-url="/common/tasks/tasks.do"
authentication-success-handler-ref="mySuccessHandler"/>
<security:logout
invalidate-session="true"
logout-success-url="/auth/login.do"
logout-url="/auth/logout.do"/>
</security:http>
<sec:global-method-security pre-post-annotations="enabled" />
<!-- Declare an authentication-manager to use a custom userDetailsService -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="authenticationService">
<!-- <security:password-encoder ref="passwordEncoder"/> -->
</security:authentication-provider>
</security:authentication-manager>
<!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
<!--
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
-->
<!-- A custom service where Spring will retrieve users and their corresponding access levels -->
<bean id="authenticationService" class="ie.premiumpower.services.AuthenticationService"/>
<bean id="mySuccessHandler" class="ie.premiumpower.services.MySuccessHandler">
</bean>
So only admin users can access /admin/** etc.
Now I want to limit users to their own url pattern based on a different attribute (their site_id which is just an int). So only users with a site_id of 1 can go to the url "/1/**" and so on.
How can I go about doing this? Just looking for a point in the right direction. Everything I've seen so far doesn't allow me to have a variable url-pattern. As in "/{variable}/".
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
See "15.3 Method Security Expressions"
You can use something like
#PreAuthorize("#value == '123'")
#RequestMapping(value="/secure")
#ResponseBody
public String aloa(#RequestParam("value") String value, Principal principal) {
return "Hello " + principal.getName();
}
This will only let you in if you provide "value=123" as a request Parameter.
You may also use #PathVariable here:
#PreAuthorize("#value == '123'")
#RequestMapping(value="/secure/{value}/data")
#ResponseBody
public String aloa(#PathVariable("value") String value, Principal principal)
If you want fine-grain access control to your domain objects, you may want to use spring-acl for such purpose. There you can define fine grained access control for any object base on user permissions. Heres the simple base on which acl is base uppon, too. you can throw in your own implementation of PermissionEvaluator and then make use of "hasPermission" inside the #PreAuthorize:
Link it in in your security config:
<global-method-security secured-annotations="disabled" pre-post-annotations="enabled">
<expression-handler ref="expressionHandler"/>
</global-method-security>
<beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</beans:bean>
create a "hasPermission" PreAuthorize constraint:
#PreAuthorize("hasPermission(#value, 'admin')")
#RequestMapping(value="/secure/{value}/data")
#ResponseBody
public String aloa(#PathVariable("value") String value, Principal principal)
Fill a PermissionEvaluator with life. Here you can bridge your domain-permission over to spring-security: The referenced value from your #RequestMapping will come in through the "targetDomainObject" in "permission" you'll find the required permission as defined in your "hasPermission" definition above.
#Component("myPermissionEvaluator")
public class MyPermissionEvaluator implements PermissionEvaluator {
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return ...;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
return ...;
}
}
you may even directly access the Principal object from within the annotations, too:
#PreAuthorize("#value == authentication.principal.title") //my pricipal is from ldap source and title is mapped in from there.
I have a SpringMVC web application that needs to authenticate to a RESTful web service using Spring Security by sending the username and password. When an user is logged, a cookie needs to be set to the user's browser and in the subsequent calls the user session is validated with another RESTful web service by using the cookie.
I've been looking everywhere, but I have not been able to find a good example on how to accomplish this, and all my attempts have been in vain.
Here is what I have in mind:
I can have two authentication-providers declared, the first checks the cookie, and if it fails for any reason it goes to the second one which checks with the username and password (will fail too if there is no username and password in that request).
Both services return the authorities of the user each time, and spring security is "stateless".
On the other hand, I have questioned myself if this approach is correct, since it's been so difficult to find an example or somebody else with the same problem. Is this approach wrong?
The reason why I want to do this instead of just JDBC authentication is because my whole web application is stateless and the database is always accessed through RESTful web services that wrap a "petitions queue", I'd like to respect this for user authentication and validation too.
What have I tried so far? I could paste the long long springSecurity-context.xml, but I'll just list them instead for now:
Use a custom authenticationFilter with a authenticationSuccessHandler. Obviously doesn't work because the user is already logged in this point.
Make an implementation of entry-point-ref filter.
Do a custom-filter in the position BASIC_AUTH_FILTER
Make a custom Authentication Provider (Struggled a lot with no luck!). I'm retrying this while I get some answers.
I was starting to use CAS when I decided to write a question instead. Maybe in the future I can consider having a CAS server in my webapp, however for the moment, this feels like a huge overkill.
Thanks in advance!
BTW, I'm using Spring Security 3.1.4 and Spring MVC 3.2.3
EDIT: I WAS ABLE TO DO IT THANKS TO #coder ANSWER
Here is some light on what I did, I'll try to document all this and post it here or in a blog post sometime soon:
<http use-expressions="true" create-session="stateless" entry-point-ref="loginUrlAuthenticationEntryPoint"
authentication-manager-ref="customAuthenticationManager">
<custom-filter ref="restAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="restPreAuthFilter" position="PRE_AUTH_FILTER" />
<intercept-url pattern="/signin/**" access="permitAll" />
<intercept-url pattern="/img/**" access="permitAll" />
<intercept-url pattern="/css/**" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
</http>
<authentication-manager id="authManager" alias="authManager">
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
<beans:bean id="restPreAuthFilter" class="com.company.CustomPreAuthenticatedFilter">
<beans:property name="cookieName" value="SessionCookie" />
<beans:property name="checkForPrincipalChanges" value="true" />
<beans:property name="authenticationManager" ref="authManager" />
</beans:bean>
<beans:bean id="preauthAuthProvider"
class="com.company.CustomPreAuthProvider">
<beans:property name="preAuthenticatedUserDetailsService">
<beans:bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<beans:property name="userDetailsService" ref="userDetailsService" />
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="userDetailsService" class="com.company.CustomUserDetailsService" />
<beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:constructor-arg value="/signin" />
</beans:bean>
<beans:bean id="customAuthenticationManager"
class="com.company.CustomAuthenticationManager" />
<beans:bean id="restAuthenticationFilter"
class="com.company.CustomFormLoginFilter">
<beans:property name="filterProcessesUrl" value="/signin/authenticate" />
<beans:property name="authenticationManager" ref="customAuthenticationManager" />
<beans:property name="authenticationFailureHandler">
<beans:bean
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login?login_error=t" />
</beans:bean>
</beans:property>
</beans:bean>
And the Custom Implementations are something like this:
// Here, the idea is to write authenticate method and return a new UsernamePasswordAuthenticationToken
public class CustomAuthenticationManager implements AuthenticationManager { ... }
// Write attemptAuthentication method and return UsernamePasswordAuthenticationToken
public class CustomFormLoginFilter extends UsernamePasswordAuthenticationFilter { ... }
// Write getPreAuthenticatedPrincipal and getPreAuthenticatedCredentials methods and return cookieName and cookieValue respectively
public class CustomPreAuthenticatedFilter extends AbstractPreAuthenticatedProcessingFilter { ... }
// Write authenticate method and return Authentication auth = new UsernamePasswordAuthenticationToken(name, token, grantedAuths); (or null if can't be pre-authenticated)
public class CustomPreAuthProvider extends PreAuthenticatedAuthenticationProvider{ ... }
// Write loadUserByUsername method and return a new UserDetails user = new User("hectorg87", "123456", Collections.singletonList(new GrantedAuthorityImpl("ROLE_USER")));
public class CustomUserDetailsService implements UserDetailsService { ... }
you can define a custom pre-auth filter by extending
AbstractPreAuthenticatedProcessingFilter.
In your implementation of
getPreAuthenticatedPrincipal() method you can check if cookie exists
and if it exists return cookie name is principal and cookie value in
credentials.
Use PreAuthenticatedAuthenticationProvider and provide your custom preAuthenticatedUserDetailsService to check if cookie is vali, if its valid also fetch granted authorities else throw AuthenticationException like BadCredentialsException
For authenticating user using username/password, add a form-login filter, basic-filter or a custom filter with custom authentication provider (or custom userdetailsService) to validate user/password
In case cookie exists, pre auth filter will set authenticated user in springContext and your username./password filter will not be called, if cookie is misisng/invalid, authentication entry point will trigger the authentication using username/password
Hope it helps
I already try the whole day, to get my custom authentication failure handler to work with Spring 3.1.3.
I think it is properly configured
<http use-expressions="true" disable-url-rewriting="true">
<intercept-url pattern="/rest/login" access="permitAll" />
<intercept-url pattern="/rest/**" access="isAuthenticated()" />
<intercept-url pattern="/index.html" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<form-login username-parameter="user" password-parameter="pass" login-page="/rest/login"
authentication-failure-handler-ref="authenticationFailureHandler" />
</http>
<beans:bean id="authenticationFailureHandler" class="LoginFailureHandler" />
My implementation is this
public class LoginFailureHandler implements AuthenticationFailureHandler {
private static final Logger log = LoggerFactory.getLogger(LoginFailureHandler.class);
public LoginFailureHandler() {
log.debug("I am");
}
#Autowired
private ObjectMapper customObjectMapper;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.debug("invalid login");
User user = new User();
user.setUsername("invalid");
try (OutputStream out = response.getOutputStream()) {
customObjectMapper.writeValue(out, user);
}
}
}
In the console I see
2013-04-11 14:52:29,478 DEBUG LoginFailureHandler - I am
So it is loaded.
With wrong username or passwort, when a BadCredentialsException is thrown, I don't see invalid login.
The Method onAuthenticationFailure is never invoked.
Instead the service redirects the browser onto /rest/login again and again...
Edit
2013-04-11 15:47:26,411 DEBUG de.pentos.spring.LoginController - Incomming login chuck.norris, norris
2013-04-11 15:47:26,412 DEBUG o.s.s.a.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-04-11 15:47:26,415 DEBUG o.s.s.a.d.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-04-11 15:47:26,416 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,426 DEBUG o.s.web.servlet.DispatcherServlet - Could not complete request
org.springframework.security.authentication.BadCredentialsException: Bad credentials
This happens in DEBUG Mode
Where is my mistake?
Judged from the logs you attached I think you've made a mistake in implementing the login process. I cannot be absolutely sure, but I guess you call ProviderManager.authenticate() in your LoginController. The method throws a BadCredentialsException that causes Spring MVC's exception handling mechanism to kick in, which of course has no knowledge about the AuthenticationFailureHandler configured for Spring Security.
From the login controller you should normally just serve a simple login form with action="j_spring_security_check" method="post". When the user submits that form, the configured security filter (namely UsernamePasswordAuthenticationFilter) intercepts that request and handles authentication. You don't have to implement that logic yourself in a controller method.
Reply to your comment:
You do use ProviderManager (it's the implementation of the autowired AuthenticationManager interface). The mistake you make is that you try to rewrite the logic already implemented and thoroughly tested in auth filters. This is bad in itself, but even that is done in a wrong way. You select just a few lines from a complex logic, and among other things you forget e.g. invoking the session strategy (to prevent session fixation attacks, and handling concurrent sessions). The original implementation invokes the AuthenticationFailureHandler
as well, which you also forgot in your method, this is the very reason of the problem your original question is about.
So you end up with an untested, brittle solution instead of nicely integrating with the framework to leverage its roboustness and full capacity. As I said, the config you posted in your answer is a definite improvement, because it uses the framework provided filter for authentication. Keep that config and remove LoginController.login(), it won't be called anyway by requests sent to /rest/login.
A more fundamental question is if it's really a good solution to use sessions and form-based login mechanism if you implement RESTful services. (On form-based login I mean that the client sends its credentials once in whatever format, and then gets authenticated by a stateful session on subsequent requests.) With REST services it's more prevalent to keep everything stateless, and re-authenticate each new request by information carried by http headers.
It's a problem with the order in the security-app-context.xml.
If I first define all my beans and then all the rest it works.
I tried a lot, so don't wonder, that it now looks a little different then in the question
<beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/rest/login" />
</beans:bean>
<beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="filterProcessesUrl" value="/rest/login" />
<beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
</beans:bean>
<beans:bean id="authenticationSuccessHandler" class="de.pentos.spring.LoginSuccessHandler" />
<beans:bean id="authenticationFailureHandler" class="de.pentos.spring.LoginFailureHandler" />
<http use-expressions="true" disable-url-rewriting="true" entry-point-ref="authenticationProcessingFilterEntryPoint"
create-session="ifRequired">
<intercept-url pattern="/rest/login" access="permitAll" />
<intercept-url pattern="/rest/**" access="isAuthenticated()" />
<intercept-url pattern="/index.html" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="chuck.norris" password="cnorris" authorities="ROLE_ADMIN" />
<user name="user" password="user" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
Does not look bad to me. Did you try to use the debug mode of your IDE ?
Did you see things like this in your logs :
Authentication request failed: ...
Updated SecurityContextHolder to contain null Authentication
Delegating to authentication failure handler ...
The AuthenticationFailureHandler will be called automatically, only if the authentication is done in one of the authentication filter : UsernamePasswordAuthenticationFilter normally in your case.
(Looking at your requirements), You don't need a custom AuthenticationFailureHandler as the with default SimpleUrlAuthenticationFailureHandler of Spring and properly implementing AuthenticationProvider should serve the purpose.
<form-login login-page="/login" login-processing-url="/do/login" authentication- failure-url ="/login?authfailed=true" authentication-success-handler-ref ="customAuthenticationSuccessHandler"/>
If you have handled the Exceptions well in Authentication Provider:
Sample Logic:
String loginUsername = (String) authentication.getPrincipal();
if (loginUsername == null)
throw new UsernameNotFoundException("User not found");
String loginPassword = (String) authentication.getCredentials();
User user = getUserByUsername(loginUsername);
UserPassword password = getPassword(user.getId());
if (!password.matches(loginPassword)) {
throw new BadCredentialsException("Invalid password.");
}
If we want the exceptions thrown to be reflected at the client interface, add the following scriplet on the JSP responding to authentication-failure-url="/login?authfailed=true"
<%
Exception error = (Exception) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
if (error != null)
out.write(error.getMessage());
%>
I am using Spring Security 3 in Struts 2 + Spring IOC project.
I have used Custom Filter, Authentication Provider etc. in my Project.
You can see my security.xml here
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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.1.xsd">
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler" />
</global-method-security>
<beans:bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" >
<beans:property name="permissionEvaluator" ref="customPermissionEvaluator" />
</beans:bean>
<beans:bean class="code.permission.MyCustomPermissionEvaluator" id="customPermissionEvaluator" />
<!-- User Login -->
<http auto-config="true" use-expressions="true" pattern="/user/*" >
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/user/showLoginPage.action" access="permitAll"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showSecondUserPage" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/user/showThirdUserPage" access="hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<form-login login-page="/user/showLoginPage.action" />
<logout invalidate-session="true"
logout-success-url="/"
logout-url="/user/j_spring_security_logout"/>
<access-denied-handler ref="myAccessDeniedHandler" />
<custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilter"/>
</http>
<beans:bean id="myAccessDeniedHandler" class="code.security.MyAccessDeniedHandler" />
<beans:bean id="myApplicationFilter" class="code.security.MyApplicationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationFailureHandler" ref="failureHandler"/>
<beans:property name="authenticationSuccessHandler" ref="successHandler"/>
</beans:bean>
<beans:bean id="successHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/user/showFirstPage"> </beans:property>
</beans:bean>
<beans:bean id="failureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/user/showLoginPage.action?login_error=1"/>
</beans:bean>
<beans:bean id= "myUserDetailServiceImpl" class="code.security.MyUserDetailServiceImpl">
</beans:bean>
<beans:bean id="myAuthenticationProvider" class="code.security.MyAuthenticationProvider">
<beans:property name="userDetailsService" ref="myUserDetailServiceImpl"/>
</beans:bean>
<!-- User Login Ends -->
<!-- Admin Login -->
<http auto-config="true" use-expressions="true" pattern="/admin/*" >
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/admin/showSecondLogin" access="permitAll"/>
<intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')"/>
<form-login login-page="/admin/showSecondLogin"/>
<logout invalidate-session="true"
logout-success-url="/"
logout-url="/admin/j_spring_security_logout"/>
<access-denied-handler ref="myAccessDeniedHandlerForAdmin" />
<custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilterForAdmin"/>
</http>
<beans:bean id="myAccessDeniedHandlerForAdmin" class="code.security.admin.MyAccessDeniedHandlerForAdmin" />
<beans:bean id="myApplicationFilterForAdmin" class="code.security.admin.MyApplicationFilterForAdmin">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationFailureHandler" ref="failureHandlerForAdmin"/>
<beans:property name="authenticationSuccessHandler" ref="successHandlerForAdmin"/>
</beans:bean>
<beans:bean id="successHandlerForAdmin"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
</beans:bean>
<beans:bean id="failureHandlerForAdmin"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/admin/showSecondLogin?login_error=1"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="myAuthenticationProviderForAdmin" />
<authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>
<beans:bean id="myAuthenticationProviderForAdmin" class="code.security.admin.MyAuthenticationProviderForAdmin">
<beans:property name="userDetailsService" ref="userDetailsServiceForAdmin"/>
</beans:bean>
<beans:bean id= "userDetailsServiceForAdmin" class="code.security.admin.MyUserDetailsServiceForAdminImpl">
</beans:bean>
<!-- Admin Login Ends -->
<beans:bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basenames">
<beans:list>
<beans:value>code/security/SecurityMessages</beans:value>
</beans:list>
</beans:property>
</beans:bean>
Uptill now you can see, url-pattern I have mentioned is hard coded. I wanted to know if there is a way to create new ROLES and PERMISSIONS dynamically, not hard coded.
Like creating new roles and permissions and saving them to database and then accessing from database. I have searched on net, but I am not able to find out how to add new entries to code.
So these are at least two questions:
How to make the granted authorities/privileges/Roles dynamic?
How to make the access restriction for the URLs dynamic?
1) How to make the granted authorities/privileges/Roles dynamic?
I will not answer this in great detail, because I believe this theme was discussed often enough.
The easiest way would be to store the complete user information (login, password and roles) in a database (3 Tables: User, Roles, User2Roles) and use the JdbcDetailService. You can configure the two SQL Statements (for authentication and for granting the roles) very nicely in your xml configuration.
But then the user needs to logout and login to get these new Roles. If this is not acceptable, you must also manipulate the Roles of the current logged in user. They are stored in the users session. I guess the easiest way to do that is to add a filter in the spring security filter chain that updates the Roles for every request, if they need to be changed.
2) How to make the access restriction for the URLs dynamic?
Here you have at last two ways:
Hacking into the FilterSecurityInterceptor and updating the securityMetadataSource, the needed Roles should be stored there. At least you must manipulate the output of the method DefaultFilterInvocationSecurityMetadataSource#lookupAttributes(String url, String method)
The other way would be using other expressions for the access attribute instead of access="hasRole('ROLE_USER')". Example: access="isAllowdForUserPages1To3". Of course you must create that method. This is called a "custom SpEL expression handler" (If you have the Spring Security 3 Book it's around page 210. Wish they had chapter numbers!). So what you need to do now is to subclass WebSecurityExpressionRoot and introduce a new method isAllowdForUserPages1To3. Then you need to subclass DefaultWebSecurityExpressionHandler and modify the createEvaluationContext method so that its first request StandartEvaluationContext calls super (you need to cast the result to StandartEvaluationContext). Then, replace the rootObject in the StandartEvaluationContext using your new CustomWebSecurityExpressionRoot implementation. That's the hard part! Then, you need to replace the expressionHandler attribute of the expressionVoter (WebExpressionVoter) in the xml configuration with your new subclassed DefaultWebSecurityExpressionHandler. (This sucks because you first need to write a lot of security configuration explicity as you can't access them directly from the security namespace.)
I would like to supplement Ralph's response about creating custom SpEL expression. His explanations helped very much on my attempt to find the right way to do this, but i think that they need to be extended.
Here is a way on how to create custom SpEL expression:
1) Create custom subclass of WebSecurityExpressionRoot class. In this subclass create a new method which you will use in expression. For example:
public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
public CustomWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
super(a, fi);
}
public boolean yourCustomMethod() {
boolean calculatedValue = ...;
return calculatedValue;
}
}
2) Create custom subclass of DefaultWebSecurityExpressionHandler class and override method createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) (not createEvaluationContext(...)) in it to return your CustomWebSecurityExpressionRoot instance. For example:
#Component(value="customExpressionHandler")
public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
#Override
protected SecurityExpressionRoot createSecurityExpressionRoot(
Authentication authentication, FilterInvocation fi) {
WebSecurityExpressionRoot expressionRoot = new CustomWebSecurityExpressionRoot(authentication, fi);
return expressionRoot;
}}
3) Define in your spring-security.xml the reference to your expression handler bean
<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
...
<security:expression-handler ref="customExpressionHandler"/>
</security:http>
After this, you can use your own custom expression instead of the standard one:
<security:authorize access="yourCustomMethod()">
This question has a very straightforward answer. I wonder why you haven't got your answer yet.
There are two things that should be cleared at least:
First, you should know that when you are using namespace, automatically some filters will be added to each URL you have written.
Second, you should also know that what each filter does.
Back to your question:
As you want to have intercept-url to be dynamically configured, you have to remove those namespaces, and replace them with these filters:
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/css/**" filters="none" />
<sec:filter-chain pattern="/images/**" filters="none" />
<sec:filter-chain pattern="/login.jsp*" filters="none" />
<sec:filter-chain pattern="/user/showLoginPage.action" filters="none" />
<sec:filter-chain pattern="/**"
filters="
securityContextPersistenceFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>
Then you have to inject your own SecurityMetadaSource into FilterSecurityInterceptor. See the following:
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource" />
</bean>
<bean id="myFilterInvocationSecurityMetadataSource" class="myPackage.MyFilterSecurityMetadataSource">
</bean>
But before that, you have to customize 'MyFilterSecurityMetadataSource' first.
This class has to implement the 'DefaultFilterInvocationSecurityMetadataSource'.
As you want to have all roles and URLs in your DB, you have to customize its getAttributes
Now see the following example of its implementation:
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public List<ConfigAttribute> getAttributes(Object object) {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
attributes = getAttributesByURL(url); //Here Goes Code
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
do you see the "Here goes your code" comment? You have to implement that method yourself.
I myself, have a table named URL_ACCESS which contains both URLs and their corresponding roles. After receiving URL from user, I look up into that table and return its related role.
As I'm working exactly on this subject, you may ask any questions... I will always answer.
You can use Voter to dynamically restrict access. Also see Get Spring Security intercept urls from database or properties
Create your model (user, role, permissions) and a way to retrieve permissions for a given user;
Define your own org.springframework.security.authentication.ProviderManager and configure is (set its providers) to a custom org.springframework.security.authentication.AuthenticationProvider; this last one should return on its authenticate method a Authentication, which should be setted with the GrantedAuthority, in your case, all the permissions for the given user.
The trick in that article is to have roles assigned to users, but, to set the permissions for those roles in the Authentication.authorities object.
For that I advise you to read the API, and see if you can extend some basic ProviderManager and AuthenticationProvider instead of implementing everything. I've done that with LdapAuthenticationProvider setting a custom LdapAuthoritiesPopulator, that would retrieve the correct roles for the user.