Spring Security: forward to original page after login with admin user after access was denied - authentication

I use Spring security to authenticate users. If a user requests a secured page, he has to authenticate over a login page. If the user is always authenticated, he will be redirected to the requested page immediatly. Moreover some pages need special access rights, and so I setup an access-denied-page temporarily. So far so good.
The scenario:
The scenario definies, that the user will get a login-form instead of a static access-denied page, so that a different user can authenticate and if authentication is successful the requested page that needs the higher privileges will open.
The actual spring configuration reads:
<security:http auto-config="true" use-expressions="true" disable-url-rewriting="true">
<security:intercept-url pattern="/index.jsp" access="permitAll" />
<security:intercept-url pattern="/loginView" access="permitAll" />
<security:intercept-url pattern="/accessDenied" access="permitAll"/>
<security:intercept-url pattern="/user" access="hasRole('ROLE_USER')" />
<security:intercept-url pattern="/admin" access="hasRole('ROLE_ADMIN')" />
<security:intercept-url pattern="/**" access="denyAll"/>
<security:form-login login-page="/loginView"
authentication-failure-url="/loginView"
default-target-url="/dirView" />
<security:logout />
<security:access-denied-handler ref="accessDeniedHandler" />
</security:http>
The accessDeniedHandler-Bean:
public class AccessDeniedServletRequestHandler implements AccessDeniedHandler {
/** {#inheritDoc} */
#Override
public void handle(HttpServletRequest req, HttpServletResponse resp,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
RequestDispatcher d = req.getRequestDispatcher("/loginView");
d.forward(req, resp);
}
}
But that implementation of AccessDeniedHandler only forwards to the loginView. After authentication of an admin the default-success-page is openend and not the original requested page. I also tried to save the original request by calling HttpServletRequest#getAttribute("javax.servlet.forward.servlet_path"), but I don't understand how to force spring security to use that original request instead of the default target url.
Besides I read about org.springframework.security.web.savedrequest.SavedRequest that is used inside spring authentication to remember the original request if an unauthenticated user requests a page. But I don't find a valid way how to use the SavedRequest in the same manner for my access denied scenario.
Thanks in advance for suggestions and solutions.

I think your requirements should be satisfied by using the RequestCache API.
If you modify your http configuration you can use the request-cache namespace element:
<security:http>
...
<security:request-cache ref="requestCache"
</security:http>
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache" />
You can also inject it into your AccessDeniedHandler. Then all you should need is a simple to saveRequest to setup the cached request which should be restored post-authentication:
public class AccessDeniedServletRequestHandler implements AccessDeniedHandler {
// Inject this into your class.
private RequestCache requestCache;
#Override
public void handle(HttpServletRequest req, HttpServletResponse resp,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
requestCache.saveRequest(req, resp);
RequestDispatcher d = req.getRequestDispatcher("/loginView");
d.forward(req, resp);
}
}
Strictly speaking, you don't actually need to do the bit with the namespace at all, since HttpSessionRequestCache is stateless (it's the internal implementation which is used if you don't override it in the namespace). So you could just create one directly in your AccessDeniedHandler class and it would still work.

Related

Spring Security With Custom Login Flow

I am trying to add spring with spring security in an existing Struts based application. It is a Partial migration. All the new development should happen in Spring.
So what I want is,
User access a Secured URL
If the user is not authenticated Spring Should redirect to a specific URL (This will be a Struts URL)
After Old Struts Module does its work of authenticating the user, It saves an Object in HTTP Session which depicts the authenticated User.
Spring Should get this Object from Session and Authenticated the User.
Redirect the User to the requested Secured URL
I have tried to used formlogin().loginpage(<Struts_URL>). Also in the Struts code after authentication, I have updated the Spring's SecurityContext.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity pHttp) throws Exception
{
pHttp.antMatcher("/**").authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/<Struts_Login_Entry>.do");
}
...
#Override
public void configure(WebSecurity pWeb) throws Exception
{
// All the Strtus URL has .do suffix so excluding it
pWeb.ignoring().antMatchers("/*.do", "/portal/**");
}
}
After Authentication in Struts
...
MyUserDetails lUserDetails = new MyUserDetails(pUserName, pRole);
Authentication auth = new UsernamePasswordAuthenticationToken(lUserDetails, null, lUserDetails.getAuthorities());
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
...
The User is getting redirected to Struts page when he accesses any Spring URL But this happens even if the user is authenticated.
I am not so familiar with Spring Security, any suggestion or help?

Login via pre authentication filter or form login in spring security

I am looking for a way where you can have two ways to get access to my website.
1) You should be able to use form-login and get your authorities and use the website.
2) You should be able to login into another website and there you should be able to press a link with a token and be logged in to my website. (I control both websites and they use the same database)
Stage 1 is completed and works well and I have made stage 2 something similar to this https://stackoverflow.com/a/9919988/1915913 and that is working as well, I get a token and I am able to verify it and login.
But my problem is, how can I make them both work for me at the same time, for the same resources. I am pretty sure I know what the problem is, I create a custom filter and i try to use the form-login filter.
That does not work, but can it? Or is there some other way i can get this functionality?
This does not work in a way that the pre-auth filter seems to take over and i cant get the normal login to work and it seems to call the pre-auth filter everytime i go to a new page in the project.
The classes I use for this are all pretty simple.
My security-app-context:
<http pattern="/**" use-expressions="true" create-session="always">
<intercept-url pattern="/login.jsp*" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<custom-filter position="PRE_AUTH_FILTER" ref="PreAuthenticatedProcessingFilter" />
<form-login
username-parameter="idnumber"
password-parameter="password" login-processing-url="/processlogin"
login-page='/login.jsp'
authentication-failure-handler-ref="myAuthErrorHandler"
authentication-success-handler-ref="mySuccessHandler"
always-use-default-target='true'
authentication-failure-url="/login.jsp?login_error=true"/>
<logout logout-url="/logout/" logout-success-url="/login.jsp" delete-cookies="JSESSIONID"/>
<session-management invalid-session-url="/">
<concurrency-control expired-url="/" max-sessions="2" />
</session-management>
</http>
<!-- form login -->
<beans:bean id="mySuccessHandler" class="is.inna.rest.login.SuccessHandler"/>
<beans:bean id="myAuthErrorHandler" class="is.inna.rest.login.AuthentificationListener"/>
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<beans:bean name="myUserDetailsService" class="is.inna.rest.login.LoginUserDetailService" />
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailsService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
<!-- Pre auth -->
<beans:bean id="userDetailsServiceWrapper" class="is.inna.rest.login.AuthUserDetailService" />
<beans:bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="userDetailsServiceWrapper"/>
</beans:bean>
<beans:bean id="PreAuthenticatedProcessingFilter" class="is.inna.rest.login.PreAuthenticatedProcessingFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
My user details services
public class AuthUserDetailService implements AuthenticationUserDetailsService<Authentication> {
#Override
public UserDetails loadUserDetails(Authentication authentication) throws UsernameNotFoundException {
String id = (String) authentication.getPrincipal();
NotandiHelper notandi = UserDAO.getNotandiByToken(id);
return new User(notandi.getUsername(), notandi.getPassword(), notandi.getAuthorities());
}
}
My pre auth filter
public class PreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if(request.getParameter("id") != null){
return request.getParameter("id");
}else if(request.getParameter("idnumber") != null){
return request.getParameter("idnumber");
}
return null;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if(request.getParameter("kt") != null){
String[] credentials = new String[2];
credentials[0] = request.getParameter("token");
credentials[2] = request.getParameter("id");
return credentials;
}
if(request.getParameter("idnumber")!= null){
String[] credentials = new String[2];
credentials[0] = request.getParameter("idnumber");
credentials[1] = request.getParameter("password");
return credentials;
}
return null;
}
When you're extending a class, you really need to understand how the base class works (one of the problems with inheritance). In this case, you are returning "false" in the case where there is no token present, which is an arbitrary choice for a method returning an object. If you look at the base class, you will see that it checks for a non-null principal and returns immediately from doAuthenticate if the value is null, causing the filter chain to proceed immediately, as if the pre-authentication filter wasn't there at all. So that's not right. Try returning null instead.
Note that the debug log should also contain a log message saying
preAuthenticatedPrincipal = null, trying to authenticate
It's also unclear from your configuration how you authenticate the token. There doesn't seem to be anything there to do that.
Note that you'll need to use the entry point for form-login if you want the login form to be displayed automatically for unauthenticated users. The http403EntryPoint will just return immediately with a 403 code.

Adding forms authentication (SimpleMembership) to webapi app

I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx

spring security stay on single url at login

here is my case.
when first time user land to site, they will pointed to login page. (lest say http://ex.com/) and when they successfully login, they'll see the other page with the same url (http://ex.com/
but, when they open the site on other tab (http://ex.com) they will pointed back to login page.
how to implement this case in my site with spring security ?
its easy to do when deal with conventional servlet. i just need to have 2 method (doGet for showing login page, and doPost for authenticating user and if its valid it will call another view).
here is my configuration :
<security:http auto-config="true">
<security:intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:form-login login-page="/login"
login-processing-url="/loginProcess"
default-target-url="/login"
authentication-failure-url="/login?login_error=1" />
<security:logout logout-url="/logout" logout-success-url="/logoutSuccess" />
</security:http>
** Edited (remove unrelated answer)
It appears you need to add a concurrent session management using Spring Security. See the following link: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/session-mgmt.html
You can inject the SessionRegistry and see if the principal is already logged-in. If he is, call the expireNow()
Or you can implement a filter on or before SessionManagementFilter in the FilterChainProxy:
The SessionManagementFilter checks the contents of the SecurityContextRepository against the current contents of the SecurityContextHolder to determine whether a user has been authenticated during the current request, typically by a non-interactive authentication mechanism, such as pre-authentication or remember-me [19]. If the repository contains a security context, the filter does nothing. If it doesn't, and the thread-local SecurityContext contains a (non-anonymous) Authentication object, the filter assumes they have been authenticated by a previous filter in the stack. It will then invoke the configured SessionAuthenticationStrategy.
- http://static.springsource.org/spring-security/site/docs/3.1.x/reference/session-mgmt.html
I think your configuration has a problem
<security:http auto-config="true">
<security:intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:form-login login-page="/login"
login-processing-url="/loginProcess"
default-target-url="<home-page-url. ex: /home>"
authentication-failure-url="/login?login_error=1" />
<security:logout logout-url="/logout" logout-success-url="/logoutSuccess" />
</security:http>
The default-target-url should point to the default page to which the application has to redirect after a successful login.
EDITED
After going through the required posted again, I think the approach is to make the controller handling /login request to handle both cases
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class AppsController {
#RequestMapping("/login")
public ModelAndView view(HttpServletRequest request,
HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
User user = authentication != null
&& authentication.getPrincipal() instanceof User ? (User) authentication
.getPrincipal() : null;
return user == null ? getLoginModelAndView() : getHomeModelAndView();
}
private ModelAndView getHomeModelAndView() {
return null;
}
private ModelAndView getLoginModelAndView() {
return null;
}
}
If there is no authenticated user present in the session the controller will return the log-in page, but once the user is logged-in then it will return a different page.
Spring security will cache the logged used to the user session and it can be retrieved using the SecurityContextHolder.

Redirect to another action in an interceptor in struts 2

I am currently in the process of learning Struts 2 and I am currently building a simple application where unverified users are redirected to a login form.
I have a login form and action functional which takes the users credentials, verifies them and stores a User object in the session however I am now trying to prevent access to pages before the login has taken place and I am trying to do this with an interceptor.
My problem is that I have written an interceptor that checks whether the User object has been saved in the session but if it has not I want to redirect to the login page and can't find any way of doing this without bypassing struts and using the HttpServletResponse.sendRedirect method
Configuration:
<package name="mypackage" extends="struts-default" namespace="/admin">
<interceptors>
<interceptor name="login" class="my.LoginInterceptor" />
</interceptors>
<default-interceptor-ref name="login"/>
<action name="login" class="my.LoginAction">
<result name="input">/admin/login.jsp</result>
<result name="success" type="redirect">/admin</result>
</action>
<action name="private" class="my.PrivateAction">
<result>/admin/private.jsp</result>
</action>
</package>
The interceptor code:
#Override
public String intercept(ActionInvocation inv) throws Exception {
Map<String, Object> session = inv.getInvocationContext().getSession();
Object user = session.get("user");
if(user == null) {
// redirect to the 'login' action here
}
else {
return inv.invoke();
}
}
The standard way is to return a special global result (eg "login") and define a global mapping from that result to your admin/login.jsp. So you just must add this line:
if(user == null) {
return "login";
}
And in your struts.xml:
<global-results>
<result name="login">/admin/login.jsp</result>
</global-results>
BTW, I'm afraid that you are replacing the default Struts2 interceptor stack with your single interceptor, normally you want to add your interceptor to the stack. Eg:
<interceptors>
<interceptor name="login" class="my.LoginInterceptor" />
<interceptor-stack name="stack-with-login">
<interceptor-ref name="login"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="stack-with-login"/>
BTW2: You must NOT apply the interceptor to your login action, of course.
You can find the complete example of struts2 with a custom Login Interceptor here
http://sandeepbhardwaj.github.io/2010/12/01/struts2-with-login-interceptor.html
great tutorial.
If you need to use send redirect, return null to avoid this problem (example redirecting from www.domain.com to domain.com):
public String intercept(final ActionInvocation invocation) throws Exception {
String url=RequestUtil.getURLWithParams(); //you should implement this
int index=url.indexOf("www");
if (index!=-1 && index<10) {
//Note: <10 to check that the www is in the domain main url
//https://localhost:8443/mycontext/myaction.action?oneparam=http://www.youtube.com/user/someuser
String redirection=url.replaceFirst("www\\.", "");
LOG.debug("Redirection from "+url+" to "+redirection);
RequestUtil.getResponse().setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
RequestUtil.getResponse().sendRedirect(redirection);
return null;
}
return invocation.invoke();
}