It is possible to check in Spring Interceptor preHandle() method if requested URL is secured by Spring Security or not (has set security="none") ?
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(isSecured(request) && !paymentRegistered())
response.sendRedirect("/payment")
return super.preHandle(request, response, handler);
}
private boolean isSecured(HttpServletRequest request){
//how to check if url has security=none
}
My problem is that after successful login I want to check if user has payed for service. If not I want to redirect to payment page. My idea is to write custom request scope filter or interceptor and check if user has registered payment in database. Problem is that I do not want to filter non secured URLs such as resources, login page, error pages etc. Also payment page (which is secured) should be available.
Maybe better idea is to write custom security filter and add custom flag to Principal object such as servicePayed alongside with other security flags: enabed, accountNonExipired etc.
I would do it writing a custom AuthenticationSuccessHandler, mainly based in the simple implementation SimpleUrlAuthenticationSuccessHandler.
In your implementation, you should overwrite onAuthenticationSuccess method, and there check if you should redirect the user to the payment page or not.
/**
* Calls the parent class {#code handle()} method to forward or redirect to the target
* URL, and then calls {#code clearAuthenticationAttributes()} to remove any leftover
* session data.
*/
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if(mustCompletePayment(authentication)){
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
}
Then just write a kind of mustCompletePayment using the authentication object, from which you must be able to check if the user must complete payment or not, or if you already made a custom UserDetailsService to check it during authentication, just check that indicator in your authentication object
EDIT:
If what you really want to do is to avoid any action for the logged user while he does not complete the payment, I would manage with granted authorities.
As I see, the key here is to translate the fact that the user has yet not paid into the authorization layer in a way you could take advantage of it.
You already have implemented the logic to discover if a user has completed payment information or not, so you could write your own UserDetailsService, so in the
UserDetails loadUserByUsername(String username)throws UsernameNotFoundException
you could check that and in case the user has not complete the payment, just erase any returning granthedAuthority from the UserDetails and let only one stating that the user must complete the payment, let's say ROLE_USER_HAS_NOT_PAID.
Then, in security http config (this is xml version but maybe you are using java config), make such a kind of mappings:
<security:http ...>
...
<security:intercept-url pattern="/secured/payment/**" access="ROLE_USER,ROLE_USER_HAS_NOT_PAID" />
<security:intercept-url pattern="/secured/**" access="ROLE_USER_HAS_PAID" />
...
</security:http>
With this config, payment page would be accessible for any user, wherever the user has paid or not, while other pages are available only for users who had already paid. Only, be carefull as you must renew the user's granthed authorities once the user has paid to made him available every page.
This way, the AuthenticationSuccessHandler should not eval other than the user granthed authorities to decide where to redirect the user. I have made this several times by building a AuthenticationSuccessHandler based on a ordered map where I configured a landing page for each of the granthed authorities which need their own landing page.
Now any logged user action is forbidden if he has cont complete payment, so a HTTP 403 would be raised while trying to access any other secured resource. But you want don't want just to block the user from doing anything else, you want to redirect it to the payment page. Here you need an AccessDeniedHandler, where you could do more or less the same check:
public class CustomAuthenticationAccessDeniedHandler extends
AccessDeniedHandlerImpl implements AccessDeniedHandler {
private String errorPage = "/error/403";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void handle(HttpServletRequest arg0, HttpServletResponse arg1,
AccessDeniedException arg2) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null && context.getAuthentication().isAuthenticated()){
if(context.getAuthentication().getAuthorities().contains("ROLE_USER_HAS_NOT_PAID")){
this.redirectStrategy.sendRedirect(arg0, arg1, "/secured/payment/pay");
return;
}
}
this.redirectStrategy.sendRedirect(arg0, arg1, this.getErrorPage());
}
public RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
#Override
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
public String getErrorPage() {
return errorPage;
}
}
This way you would redirect users which still must pay to your payment page and in any other case to a default 403 page
Don't know if there's a way to get such information from Spring Security. But maybe if you do not have a lot of urls which are not secured than you can do something like this:
private boolean isSecured(HttpServletRequest request) {
String requestUri = request.getRequestURI();
return !(request.getMethod().equalsIgnoreCase("GET")
&& (requestUri.contains("error/")
|| requestUri.startsWith("resources/"))
);
}
Or move those non-secured resources to some common start path and use the idea described in the code above.
Maybe you will find a way to do that, but IMHO you should not, because it is likely to require to dive in Spring Security internals.
If you want to only use Spring Security the way it was designed for, you could implement a custom AccessDecisionVoter. For example, if could only vote for one single security attributes starting with PAYMENT. You put that security attribute in spring security configuration:
<security:intercept-url pattern="/..." access="PAYMENT,ROLE_ADMIN"/>
to restrict access to user having payed or having the ADMIN role
To declare a custom voter, you must replace the default access decision manager. First you declare it:
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="your.package.PaymentVoter"/>
</list>
</constructor-arg>
</bean>
Then you insert it in your <http> element:
<http access-decision-manager-ref="accessDecisionManager"/>
Related
Is this possible to manage multiple login page with spring authorization server?
Suppose, we have 2 client and both client want a different login page
client 1 need /login url
client 2 need /login2 url..
I believe the question is how to brand a login page based on the current clientId. We can use any technique available in Spring Security, as it is fully available and not hidden when using Spring Authorization Server.
As you point out, one way to handle this would be to perform a custom redirect when authentication is required. This would be handled in a custom AuthenticationEntryPoint. You can build a delegate with a mapping of clientIds to login urls. Normally, I'd encourage you to try it yourself (learning new things is fun, right!?), but in this case, here's an example:
public class BrandedAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Map<String, AuthenticationEntryPoint> authenticationEntryPoints;
private final AuthenticationEntryPoint defaultEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
public BrandedAuthenticationEntryPoint(Map<String, String> loginUrls) {
Map<String, AuthenticationEntryPoint> map = new HashMap<>();
loginUrls.forEach((clientId, loginUrl) ->
map.put(clientId, new LoginUrlAuthenticationEntryPoint(loginUrl)));
this.authenticationEntryPoints = Collections.unmodifiableMap(map);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
AuthenticationEntryPoint delegate = this.defaultEntryPoint;
// Attempt to resolve a specific login url based on clientId
String clientId = request.getParameter("clientId");
if (clientId != null) {
delegate = this.authenticationEntryPoints.getOrDefault(clientId, this.defaultEntryPoint);
}
delegate.commence(request, response, authException);
}
}
SAS and Form Login are two different filter chains in the default sample, so you would apply this in the normal way on both filter chains:
http.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new BrandedAuthenticationEntryPoint(...))
);
More information on AuthenticationEntryPoint is available in the reference docs.
Given an authentication mechanism of type FORM defined for a Java web app, how do you capture the login performed event before being redirected to requested resource? Is there any kind of listener where I can put my code to be executed when a user logs in?
I feel like defining a filter is not the best solution, as the filter is linked to the resource and would be invoked even when the user is already authenticated and asking for a resource. I'm wondering if there's some class/method triggered only by login event.
There's no such event in Java EE. Yet. As part of JSR375, container managed security will be totally reworked as it's currently scattered across different container implemantations and is not cross-container compatible. This is outlined in this Java EE 8 Security API presentation.
There's already a reference implementation of Security API in progress, Soteria, developed by among others my fellow Arjan Tijms. With the new Security API, CDI will be used to fire authentication events which you can just #Observes. Discussion on the specification took place in this mailing list thread. It's not yet concretely implemented in Soteria.
Until then, assuming FORM based authentication whereby the user principal is internally stored in the session, your best bet is manually checking in a servlet filter if there's an user principal present in the request while your representation of the logged-in user is absent in the HTTP session.
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String username = request.getRemoteUser();
if (username != null && request.getSession().getAttribute("user") == null) {
// First-time login. You can do your thing here.
User user = yourUserService.find(username);
request.getSession().setAttribute("user", user);
}
chain.doFilter(req, res);
}
Do note that registering a filter on /j_security_check is not guaranteed to work as a decent container will handle it internally before the first filters are hit, for obvious security reasons (user-provided filters could manipulate the request in a bad way, either accidentally or awarely).
If you however happen to use a Java EE server uses the Undertow servletcontainer, such as WildFly, then there's a more clean way to hook on its internal notification events and then fire custom CDI events. This is fleshed out in this blog of Arjan Tijms. As shown in the blog, you can ultimately end up with a CDI bean like this:
#SessionScoped
public class SessionAuthListener implements Serializable {
private static final long serialVersionUID = 1L;
public void onAuthenticated(#Observes AuthenticatedEvent event) {
String username = event.getUserPrincipal().getName();
// Do something with name, e.g. audit,
// load User instance into session, etc
}
public void onLoggedOut(#Observes LoggedOutEvent event) {
// take some action, e.g. audit, null out User, etc
}
}
You can use Servlet filter on the j_security_check URI. This filter will not be invoke on every request, but only on the login request.
Check the following page - Developing servlet filters for form login processing - this works in WebSphere App Server, and WebSphere Liberty profile.
Having such filter:
#WebFilter("/j_security_check")
public class LoginFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter called 1: " +((HttpServletRequest)request).getUserPrincipal());
chain.doFilter(request, response);
System.out.println("Filter called 2: " + ((HttpServletRequest)request).getUserPrincipal());
}
gives the following output:
// on incorrect login
Filter called 1: null
[AUDIT ] CWWKS1100A: Authentication did not succeed for user ID user1. An invalid user ID or password was specified.
Filter called 2: null
// on correct login
Filter called 1: null
Filter called 2: WSPrincipal:user1
UPDATE
Other possible way to do it is to use your own servlet for login, change the action in your login page to that servlet and use request.login() method. This is servlet API so should work even in Wildfly and you have full control over login. You just need to find out how wildfly passes the originally requested resource URL (WebSphere does it via cookie).
Servlet pseudo code:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String user = request.getParameter("j_username");
String password = request.getParameter("j_password");
try {
request.login(user, password);
// redirect to requested resource
} catch (Exception e) {
// login failed - redirect to error login page
}
I have controller method, which annotated with
#RequestMapping(value = "/someting")
#PreAuthorize("hasAnyRole('ROLE_ACTIVE')")
...
When users without it role transit on this mapping I want to make the users without the appropriate role of the redirect to the home page and displays an alert, the fact that access is denied.
To solve this problem I make custom AccessDeniedHandler, which works perfectly, but only for authenticated users
For users without authentication I found AuthenticationEntryPoint
It looks like
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(httpServletRequest);
if(flashMap != null) {
Alerts.addWarningAlert(flashMap, "access denied");
}
httpServletResponse.sendRedirect("/");
}
}
My alert can be added only to flash attributes or model of my main page, but flash map in this method always have null value
How I can solve it without redirecting to other controller, which then redirects to main page and add value to model? Or can I add my flash attributes to http servlet response?
It was possible using Session attributes. I added attribute and then take this attribute from Session in alerts handler.
I have a custom field along with "j_username" and "j_password" on my login.jsp, that I need to authenticate the user. I am using a CustomUsernamePasswordAuthenticationFilter to access the custom field as follows.
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String myCustomField= request.getParameter("myCustomField");
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
return super.attemptAuthentication(request, response);
}
}
I tried accessing the session in loadByUsername method of UserDetailsService class but I get an error. Here is the code for my custom UserDetailsService.
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession();
User userObject = dbObject.retrieveUser(userName,myCustomParameter)
// code here to retrieve my user from the DB using the userName and myCustomParameter that was retrieved from login.jsp and put in the session. Get the custom parameter from the session here.
if (userObject == null)
throw new UsernameNotFoundException("user not found");
return new AuthenticationUserDetails(userObject);
}
Is there any way where I can access this custom parameter for authentication? Sending it through the session doesn't seem to be working.
Wouldn't the session be created AFTER the authentication takes place. So a new authenticated session might be created after your call to attemptAuthentication
Here's the spring doc on the Abstract class you're implementing
http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication%28javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20org.springframework.security.core.Authentication%29
You might be losing the session attribute by the time loadByUsername is called.
I ran into the exact problem.
The problem appeared to be that the RequestAttributes was not bound to the current thread. To make it work, I had to explicitly bind it to the current thread.
In CustomUsernamePasswordAuthenticationFilter, after the statement
request.getSession().setAttribute("CUSTOM_FIELD", myCustomField);
Add:
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
This worked for for me.
I am using spring security 3 and would like to display the time when user logged in to current session.
Does Spring security provide any such token?
Thanks,
- Akshay
The most reliable option would be customizing your Spring Security filter chain to save a timestamp in the user's session when a successful login occurs. Then you would access it in the same way you access any session attribute.
According to the documentation, you can add your own filters to the Spring Security filter chain.
You could add a filter after UsernamePasswordAuthenticationFilter, if you are using http/form-login, or after BasicAuthenticationFilter, in case of http/http-basic, so we guarantee that the session is already created.
To cover both, you can add a filter after the last one, and add the information to the session.
Declare your filter-bean:
<bean id="myFilter" class="com.MyFilter"/>
Add it to the chain, right after BasicAuthenticationFilter:
<http>
<custom-filter ref="myFilter" after="BASIC_AUTH_FILTER"/>
...
Your doFilter method should look like:
private static final String LOGGED_TIME_KEY = "LOGGED_TIME";
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(LOGGED_TIME_KEY) == null) {
session.setAttribute(LOGGED_TIME_KEY, new Date());
}
}
chain.doFilter(req, res);
}
Keep in mind that you can use other hooks. You can add it even to your AuthenticationProvider.
EDIT:
There is a easier way to do that, if you are using form-login.
You can use a custom AuthenticationSuccessHandler. To define it, update your form-login tag, adding the attribute authentication-success-handler-ref.