Custom Access Rules for Spring Security - spring

Typically you define some intercept-url patterns to configure access to pages with spring security
<http use-expressions="true">
<intercept-url pattern="/**/secure/**" access="hasRole('ROLE_SECURE_USER')" />
...
</http>
We now have pages with url's that are not known beforehand. But we can write a piece of code to decide whether a specific page should be protected or not, i.e. we can provide a service that returns true if the page has to be protected. So what we'd like to do is something like this:
<http use-expressions="true">
<intercept decide="#service.mustProtect()" access="hasRole('ROLE_SECURE_USER')" />
...
</http>
How can this be achieved with Spring? Do we have to write a custom filter? How would you implement such a filter?

Actually, it was quite easy to solve our problem by injecting a custom filter just before the FilterSecurityInterceptor. You can then throw an AccessDeniedException in the filter's doFilter method to trigger authentication.
Spring security config:
<http use-expressions="true">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="accessFilter"/>
...
</http>
<beans:bean id="accessFilter" class="xyz.AccessFilter" />
Filter:
public class AccessFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!currentUserCanAccessPage(request)) {
throw new AccessDeniedException();
}
chain.doFilter(request,response)
}
private boolean currentUserCanAccessPage(ServletRequest request) {
//implement
}
}

What the <intercept-url> tags do is simply populating a repository (called SecurityMetadataSource) where RequestMatchers are mapped to ConfigAttributes. RequestMatchers are generated based on the pattern attribute, while ConfigAttributes are just holding the string specified in the access attribute.
When an incoming request hits the FilterSecurityInterceptor filter, it will iterate over the list of these mappings to find the first entry, where the RequestMatcher indicates a match, in order to determine what kind of access restrictions it has to enforce (described by the mapped ConfigAttribute).
Now, if you could put your own RequestMatcher implementation to this map, your requirement would be basically solved. The difficulty is that the namespace configuration does not cater for this use-case, it's only able to interpret the pattern attribute either as AntPathRequestMatcher or as RegexRequestMatcher.
This means that you will have to configure the security infrastructure at the bean level, because the <http> element creates its own FilterSecurityInterceptor that cannot be replaced.
In this article you can find great help on how to write this kind of manual security configuration.

Related

Spring Security in conjuction with Spring repositories to secure and authenticate various parts of a website

I was looking for a concrete, serious and complete example on how to use Spring security in a Spring Boot application that uses Spring data repositories to access the database and therefore to query about registered users.
I've seen that it's easily protected a range of webpages using Spring security by overriding the configure method, for example with the following options:
http.authorizeRequests()
.antMatchers("/", "/css/**", "/js/**", "/vendor/**", "/templates/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
This code protects users for example from accessing http://localhost:3000/home/users/, but allows then to access http://localhost:3000/login or simply http://localhost:3000.
I've been reading around about Spring security, but I can't get how can I protect the different parts of an application, for example, when a user has logged in to the website, and prohibit him from accessing from example http://localhost:3000/home/users/another_user, and in general to control the access of a logged user to all parts of the website.
I'm using Spring data repositories to manipulate data of the database through the entities.
Do you know about an example that uses Spring security in conjunction with Spring repositories (and if necessary other tools) to protect (and authenticate) different parts of a website? A (video-)tutorial may also be useful.
Thanks for any help.
Note: I've looked at the sagan website's repository, but it's quite complex to understand what's going on...
As noted above ACLs are one option however an alternative and possibly simpler solution may to be to apply security at the method level.
See section 15.3.
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
So supposing you have an URL /users/123 where 123 is the current user and which delegates to a service layer method to load the user then how do I prevent the user from tampering with the URL and seeing data returned by e.g. /users/456.
One approach is to apply method level security via the #PostAuthorize annotation:
#PostAuthorize("hasPermission(returnObject, null)")
public User findById(Long id) {
return repository.findOne(id);
}
The security checks are delegated to an implementation of org.springframework.security.access.PermissionEvaluator
An implementation could look like the below:
public class BasePermissionsEvaluator implements PermissionEvaluator {
public boolean hasPermission(Authentication authentication, Object domainObject) {
return hasPermission(authentication, domainObject, null);
}
#Override
public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) {
boolean hasPermission = true;
//User is my custom class representing a logged in user
//UserEntity is my custom interface implemented by entities associated with specific user
//If user is an Admin allow access
//Otherwise allow access if logged in user 'owns' the DomainObject instance
User user = (User) authentication.getPrincipal();
if(! user.isAdmin()){
if (domainObject instanceof UserEntity) {
User owner = ((UserEntity) domainObject).getOwner();
hasPermission = user.equals(owner);
}
}
return hasPermission;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
return false;
}
}
Configuration of the PermissionEvaluator looks like the following in XML so you would need to convert to Java config:
<security:global-method-security
pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="permissionEvaluator" />
</bean>
<bean id="permissionEvaluator" class="com.mycompany.BasePermissionsEvaluator" />
The following outlines converting the XML config to Java config:
https://spring.io/blog/2013/07/04/spring-security-java-config-preview-method-security/#custom-method-security
So in your existing security configuration class it looks like you would add:
#EnableGlobalMethodSecurity(prePostEnabled=true) //ADD THIS
public class MySecurityConfig{
#Override
protected MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
//SET TO OUR CUSTOM PERMISSIONS HANDLER DETAILED ABOVE
expressionHandler.setPermissionEvaluator(new BasePermissionsEvaluator());
return expressionHandler;
}
}
This is called Access Control, or "Domain Object Security" in Spring Security.
http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#domain-acls
You've got a lot of reading to do!
You probably want to combine it with Spring Data JPA, so that SDJ only returns the records it should. An example here:
https://github.com/spring-projects/spring-data-examples/tree/master/jpa/security
Basically you're going to be adding some "row owner" info to your tables, and getting SDJ and SS to work together to control access, like:
#Query("select o from BusinessObject o where o.owner.emailAddress like ?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
List<BusinessObject> findBusinessObjectsForCurrentUser();
An alternative it to use a database server that supports Row Security, like PostgreSQL, and handle your access control right in the db.

Where to put custom post-authentication code using UsernamePasswordAuthenticationFilter

I'm using Spring and custom implementation of UsernamePasswordAuthenticationFilter. I want to perform some custom code after successful authentication (for example: log a message with username that just got authenticated).
Which method should I override or how to register a handler for successful authentication ?
Is it good idea to override successfulAuthentication() method, put there my custom code and finish it with call to original method (super.successfulAuthentication();) ? Or there is some other best practise?
My approach for performing custom tasks after a successful
authentication is to use a Custom Authentication Success Handler in
Spring Security.
You can achieve this as below:
Create your custom AuthenticationSuccessHandler like TMGAuthenticationSuccessHandler. I have created a sample code which redirects user to the password change page, if the user is detected to be using the default machine generated password.
#Component("tMGAuthSuccessHandler")
public class TMGAuthSuccessHandler implements AuthenticationSuccessHandler {
private AuthenticationSuccessHandler target = new SavedRequestAwareAuthenticationSuccessHandler();
#Autowired
private UserService userService;
private static final Logger LOGGER = LoggerFactory.getLogger(TMGAuthSuccessHandler.class);
#Override
public void onAuthenticationSuccess(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Authentication authentication)
throws IOException, ServletException {
if (hasDefaultPassword(authentication)) {
LOGGER.debug("Default password detected for username: " + authentication.getName());
servletResponse.sendRedirect("changePassword");
} else {
target.onAuthenticationSuccess(servletRequest, servletResponse, authentication);
}
}
/**
* Checks whether default password is used in login.
*/
private boolean hasDefaultPassword(Authentication authentication) {
String username = authentication.getName();
User user = userService.findOnUsername(username, true, false, false, false);
if (user != null && user.getLoginAuditTrail() != null && user.getLoginAuditTrail().isDefaultPasswordUsed() != null) {
return user.getLoginAuditTrail().isDefaultPasswordUsed();
}
return false;
}
/**
* Proceeds to the requested URL.
*/
public void proceed(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Authentication authentication) throws IOException,
ServletException {
target.onAuthenticationSuccess(servletRequest, servletResponse, authentication);
}
}
Modify the securityContext.xml or similar file that contains spring security related configurations. Add this customHander to http configuration as authentication-success-handler-ref="tMGAuthSuccessHandler". Code snippet is shown below:
<security:http use-expressions="true" authentication-manager-ref="webAppAuthManager">
<!-- signin and signout -->
<security:intercept-url pattern="/signin" access="permitAll" />
<security:intercept-url pattern="/logout" access="permitAll" />
<security:intercept-url pattern="/accessDenied" access="permitAll"/>
<security:intercept-url pattern="/**" access="isAuthenticated()" />
<!-- sign in Configuration -->
<security:form-login login-page="/signin"
username-parameter="username"
password-parameter="password"
authentication-failure-url="/signin?authFail=true"
authentication-success-handler-ref="inoticeAuthSuccessHandler" />
<security:logout logout-url="/signout" invalidate-session="true" delete-cookies="JSESSIONID" logout-success-url="/signin?logout=true" />
</security:http>
You are good to go now.
Reference credit: How to use custom filter with authentication-success-handler-ref equivalent in spring security
You have at least two options:
AuthenticationSuccessHandler
ApplicationListener<E extends ApplicationEvent> with AbstractAuthenticationEvent

Adding Parameters to Spring Security Logout

Hai can I add parameters to Spring Security Logout. I have configured logout request in security.xml as below.
<sec:logout invalidate-session="true" logout-success-url="/logoutsuccess.do" logout-url="/logout.do" />
while Login, after successful login, I am validating various aspects of user account on Custom Success Handler to validate the Account Subscription Status, Approve/DisApprove etc and based on it will send Logout request if any of the condition fails. I would like to display custom error message on logout page and so would like to send that parameter along with logout request.
Write a custom logout handler. Put attribute success-handler-ref in xml configuration file.
e.g
<sec:logout invalidate-session="true" logout-success-url="/logoutsuccess.do"
logout-url="/logout.do"
success-handler-ref="YourCustomLogoutSuccessHandler"/>
Then extend the SimpleUrlLogoutSuccessHandler or implement the LogoutHandler.
e.g
public class YourCustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//Do what you want to do here
//.......
//below does the 'standard' spring logout handling
super.onLogoutSuccess(request, response, authentication);
}
}
An alternate way of doing this is shown here. This also shows how to redirect to a particular url and manually handle session management.

Spring Security SWF : How to redirect to different flows based on some condition

I am using spring security along with spring web flow. The problem is that I need to redirect user to two different pages based on some condition during logging.
If the user is a first time logging user he will be redirected to firstTimeuser.jsp otherwise he will be redirected to homepage.jsp.
In db side i have a field IS_FIRST_TIME_USER which will be true for first time users.
so in my LOGIN table I have id,username,password,IS_FIRST_TIME_USER fields.
In spring-security.xml I have
<http auto-config="true">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login authentication-success-handler-ref="authenticationSuccessHandler"
login-page="/basic"
default-target-url="/basic1"
authentication-failure-url="/basic?error=true"
username-parameter="username"
password-parameter="password" />
<logout logout-success-url="/basic?logout" />
</http>
Yes it is possible by providing a custom implementation of AuthenticationSuccessHandler using attribute authentication-success-handler-ref.
For example see here
Note: When using this pattern, you shouldn't use default-target-url
Simple Implementation in your case would be something like below
#Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if(isFirstTimeLogin(authentication.getName())) {
response.sendRedirect("/firstTimeuser");
} else {
response.sendRedirect("/homepage");
}
}
private boolean isFirstTimeLogin(String username) {
//code to access your DAO and figure out whether this is first time login or not
//Code to access your DAO and update the flag in database
return true;
}
}

remember-me and authentication-success-handler

i have strange issue of for login sucess and redirect to page.
below is my spring security configuration.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/login.hst**" access="anonymous or authenticated" />
<intercept-url pattern="/**/*.hst" access="authenticated" />
<form-login login-page="/login.hst"
authentication-failure-url="/login.hst?error=true"
authentication-success-handler-ref="loginSucessHandler" />
<logout invalidate-session="true" logout-success-url="/home.hst"
logout-url="/logout.hst" />
<remember-me key="jbcpHaverERP" authentication-success-handler-ref="loginSucessHandler"/>
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
LoginSuessHandler class:
#Service
public class LoginSucessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
...
super.setUseReferer(true);
super.onAuthenticationSuccess(request, response, authentication);
}
}
now problem of redirect to requested page on success. if i directly refer to any secure url spring redirects me to login page and on successful login to original requested link.
but this is not working in case if user had earlier selected remember-me and then closing browser and now requesting direct URL, he is being properly authenticated but instead of redirecting him to requested page spring redirects to /. i have checked log and some spring source code and found it is not able to determine target url.
i have tried to set refer but referer value is null. but one strange thing i have noticed that in spring security configuration if i remove authentication-success-handler from remember-me configuration then it works.
<remember-me key="jbcpHaverERP" authentication-success-handler-ref="loginSucessHandler"/>
not able to figure out issue. is authentication-success-handler implementation requied to be different for form login and remember-me?
Remember-me differs from form-login in that authentication occurs during the actual request the user makes. For form-login, the user must first be redirected to the login page, submit the login form and after that they are redirected to the original target (which is usually cached in the session). So form-login requires a redirect, whereas remember-me doesn't. With a remember-me request, the user can be authenticated, and the request allowed to proceed without any intervention.
The primary purpose of an AuthenticationSuccessHandler is to control the navigation flow after authentication, so you wouldn't normally use one with remember-me at all. Using SavedRequestAwareAuthenticationSuccessHandler isn't a good idea, as there won't be a saved request available. If there is no saved request, then by default it will perform a redirect to "/" as you have observed.
If all you want is to add some functionality during a remember-me login, then you can implement the AuthenticationSuccessHandler interface directly without performing a redirect or a forward. As I explained above, you can't use the same implementation for form-login, since the current request is the submission of the login form (usually to the URL j_spring_security_check), and not a request to a URL within your application. So you need a redirect for form-login.
You would rather use ApplicationListener and look for the event InteractiveAuthenticationSuccessEvent.
InteractiveAuthenticationSuccessEvent has a property generatedBy which will be the filter, ie UsernamePasswordAuthenticationFilter (form logins) and RememberMeAuthenticationFilter (remeber me logins)
#Component
class AuthenticationApplicationListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
//do something
}
}
using a custom implementation of AuthenticationSuccessHandler on rememberMe will cause problems. Take a look at the flow in RememberMeAuthenticationFilter. if the successHandler is used, the filter chain is bypassed
Using an AuthenticationSuccessHandler does not work. As stated in another answer, the spring security filter chain will be bypassed!
What works, is to use an ApplicationListener - as another answer also proposes. But to find out, if your user is authenticated by remember me, the idea to use InteractiveAuthenticationSuccessEvent.getGeneratedBy() is not working: getGeneratedBy returns Class<T>, that means a generic. Therefore at runtime you cannot find out, if T is a RememberMeAuthenticationFilter.
What worked fine for me: Use InteractiveAuthenticationSuccessEvent.getAuthentication().
Here an example (by the way: #EventListener is used since Spring Security 4.2 - if you use an earlier version, do the following via implementing ApplicationListener<InteractiveAuthenticationSuccessEvent>):
#Component
public class AuthenticationApplicationListener {
#EventListener
public void handleInteractiveAuthenticationSuccess(InteractiveAuthenticationSuccessEvent event) {
if (RememberMeAuthenticationToken.class.isAssignableFrom(event.getAuthentication().getClass())) {
.... do some stuff
}
}
}
You should implement different authentication-success-handler for login form and for remember-me.
If you want to perform redirect in remeber-me handler you can use SimpleUrlAuthenticationSuccessHandler and set DefaultTargetUrl.
public class RememberMeAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// ...
super.setAlwaysUseDefaultTargetUrl(true);
super.setDefaultTargetUrl(request.getRequestURL().toString());
super.onAuthenticationSuccess(request, response, authentication);
}

Resources