It's fairly easy to use custom JSP login page in Spring Security. Our application is based on Vaadin though and I don't want to have JSP login page. What I want is custom fancy login window created as Vaadin widget.
Well technically I can use Vaadin's FormLayout and name fields like j_username and j_password ... but this is Java class and not JSP file, so what do I specify in http Spring Security element? I mean:
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='MyLoginWindow.java or what?' />
</http>
I'm totally unfamiliar with Spring Security, but one of our guys at Vaadin made a demo application a couple of years ago https://github.com/peholmst/SpringSecurityDemo. I don't know if it's still up-to-date or if it even answers your question, but perhaps you could have a look and see for yourself if you can get any answers from there. Otherwise you could perhaps ping Petter personally and see if he has any fresh ideas about the topic.
Use LoginForm and in LoginListener use something like this
try {
val authentication = new UsernamePasswordAuthenticationToken(name, pass)
SecurityContextHolder.getContext.setAuthentication(authenticationManager.authenticate(authentication))
} catch {
case e: AuthenticationException => {
SecurityContextHolder.clearContext()
}
}
Please see below my approach. The code shows the functionality that occurs when clicking the login button.
loginBtn.addListener(new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
// Getting the helper for working with spring context
// found here https://vaadin.com/wiki/-/wiki/Main/Spring%20Integration
SpringContextHelper helper = new SpringContextHelper(getApplication());
// Get the providerManagerBean
ProviderManager authenticationManager = (ProviderManager)helper
.getBean("authenticationManager");
// Get entered data for name and password
String name = usernameEntered;
String password = passwordEntered;
// Validation
if (StringUtils.isBlank(name) || StringUtils.isBlank(password)) {
getWindow().showNotification("Username or password cannot be empty",
Notification.TYPE_ERROR_MESSAGE);
} else {
try {
// Security functionality goes here
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(name, password);
Authentication authentication = authenticationManager.authenticate(token);
// Set the authentication info to context
SecurityContextHolder.getContext().setAuthentication(authentication);
// During the authentification the AppUser instance was set as
// details, for more info about the user
AppUser user = (AppUser) authentication.getDetails();
if (user != null) {
// Switch the view after succesfull login
getApplication().getMainWindow().setContent(new ComboBoxUserStartsWith());
}
} catch (AuthenticationException e) {
// Display error occured during logining
getWindow().showNotification(e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
}
}
}
});
Related
I have an ASP.NET MVC application with ActionFilters for Authentication and no Forms Authentication. "SegurancaAction" is the attribute responsible for validating authentication and exists in every controller endpoint except in the login ones (as expected).
I'm facing a problem in which sometimes I try to access one of my controllers and the GET request goes to my login endpoint. In the method Application_BeginRequest at Global.asax, I can see the very first attempt is at 'security/login' (the route to my login endpoint) instead of the one I want. I can also see this endpoint being called in debugging apps such as Fiddler, or ASP.NET Trace or Glimpse MVC5.
Besides calling the wrong action, once I login again this issue keeps happening for the same endpoint I was trying to access, redirecting my site to the login page over and over.
SegurancaAction:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Autenticacoes autenticacao = _authApp.IsAutenticado(filterContext.HttpContext.Session.SessionID);
if (autenticacao == null)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized);
else
{
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
return;
}
}
else
{
// other stuff
}
}
SecurityController:
[HttpPost]
[ConfigAction]
public ActionResult Login(vm_Login login)
{
if (ModelState.IsValid)
{
if (!String.IsNullOrEmpty(login.Login) && !String.IsNullOrEmpty(login.Senha))
{
Entidades entidade = _entidadeApp.GetByUsuarioSenha(login.Login, login.Senha);
if (entidade == null)
{
ViewBag.FalhaAutenticacao = "As credenciais informadas não conferem!";
return View("Login");
}
else
{
string encryptionKey = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];
var a = _autenticacaoApp.Autenticar(entidade.Id, encryptionKey, login.Senha, HttpContext.Session.SessionID);
}
Response.RedirectPermanent("~/principal/index");
}
}
else
{
ViewBag.FalhaAutenticacao = "É necessário informar o usuario e a senha!";
}
return View();
}
All _autenticacaoApp.Autenticar(...) method does is to create an authentication entry on the database, it's a completely custom code.
Does anyone know why this issue happens? Sometimes I can reproduce it by deleting the cookies that contain ASP.NET_Session ID and RequestVerificationToken. So far I know those cookies are automatically generated and I notice that sometimes when I login again they are not re-generated.
I figured out the issue. It was this "RedirectPermanent" method being used here:
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
It tells the browser that the resource I'm trying to access is no longer available and is now located at this new Url. The browser records this information and always redirects to the new resource.
I just changed it to use "Redirect" instead.
I am investigating the feasibility of using a wildfly custom login module.
The client will pass the mobile device id to the server as part of the login. I will check that the username and password are correct in the usual way then I need to check that the mobile device is approved to use the service.
The idea is that I will have a restful webservice method login that calls HttpServletRequest.login(u, p).
How do I get hold of the mobile device id inside the login module in the HttpServletRequest?
I could login and if that succeeds then test the device id in the webservice and if that is not approved, log the user out. But that seems rather messy.
What is the correct way of doing this?
EDIT
FEED BACK: I did it the way chris suggested. I implemented my own version of the CallBackHandler and an implementation of the Callback interface, inside the login method of my login module I do the following:
public boolean login() throws LoginException {
boolean login = super.login();
if (login) {
UuidCallback uuidCallback = new UuidCallback();
try {
super.callbackHandler.handle(new Callback[]{uuidCallback});
} catch (Exception e) {
LoginException le = new LoginException("Failed to get uuid");
le.initCause(e);
throw le;
}
System.out.print("Device UUID: "+uuidCallback.getUuid());
}
return login;
}
Inside the web service login method :
#Path("/login")
#Produces({ "application/json" })
public class LoginWebService {
#POST
public Response login(#Context HttpServletRequest request) throws LoginException {
CallbackHandler callbackHandler = new MyCallbackHandler(request.getParameter("username"), request.getParameter("password"), request.getParameter("uuid"));
Subject subject = new Subject();
LoginContext loginContext = new LoginContext("securityDomain", new subject, callbackHandler);
loginContext.login();
MyPrincipal principal = subject.getPrincipals(MyPrincipal.class).iterator().next();
}
}
You could also just set the uuid on the callback handler and then call getUUID() on the callback handler inside the LoginModule.login method. But I opted to go with the design even though it does not quite make sense to me.
I was still getting 403 when logged in and trying to access protected resources it turns out that if auth-constraint/role-name is *, you must supply at least one security-role.
<login-config>
<auth-method>FORM</auth-method>
<realm-name>mydomain</realm-name>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name>rest</web-resource-name>
<url-pattern>/rest/app/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<!-- after login there is a 403 on protected resources if no role and role-name * -->
<security-role>
<role-name>user</role-name>
</security-role>
All my users have a role user, which gives them access. I was able to get it working by excluding the security-role but then auth-constraint/role-name must be set to a literal role, in my case: "user"
I would suggesting creating a LoginContext and pass thru an implementation of a CallbackHandler. In the callbackhandler cater for the extra UUID property.
While this code works for me, you will need to update it to cater for the extra UUID property
public class NamePasswordCallbackHandler implements CallbackHandler {
private final String username;
private final String password;
private NamePasswordCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback current : callbacks) {
if (current instanceof NameCallback) {
((NameCallback) current).setName(username);
} else if (current instanceof PasswordCallback) {
((PasswordCallback) current).setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(current);
}
}
}
}
I created my own implementation of the Configuration object (shown below as JBossJaasConfiguration).
Then pass thru this callbackhandler to your LoginContext:
CallbackHandler cbh = new NamePasswordCallbackHandler(username, password);
Configuration config = new JBossJaasConfiguration("mysqldomain");
LoginContext loginContext = new LoginContext("mycontext", new Subject(), cbh, config);
loginContext.login();
The property "mysqldomain" relates to the security-domain name in your standalone.xml
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
<security-domain name="mysqldomain" cache-type="default">
<authentication>
<login-module code="com.soccerx.security.DatabaseServerLoginRealm" flag="required">
<module-option name="dsJndiName" value="java:jboss/datasources/SoccerSoftwareDS"/>
<module-option name="principalsQuery" value="select userId, tenantId, password, salt from User where username=? and StatusId != 2"/>
<module-option name="rolesQuery" value="select Role, 'Roles' from User where Username=?"/>
<module-option name="password-stacking" value="useFirstPass"/>
<module-option name="principalClass" value="com.soccerx.security.DatabasePrincipal"/>
</login-module>
</authentication>
</security-domain>
<security-domains>
</subsystem>
While this is not a complete solution for your needs, I expect you can modify it to ensure that login fails should the UUID not match.
Note: You will need to cater for this in your CustomDatabaseLoginRealm, as defined in your standalone.xml. Meaning access the username/password/uuid and compare it to the values in the database.
More more documentation see here
Instead of calling HttpServletRequest.login() method directly from your restful webservice, you could configure a security-domain in Wildfly.
This security-domain should be referenced in your webapp web.xml file login-config element, with a security-constraint, to enable JAAS
Here is an example of web.xml file which declares a security-constraint and a login-config (with BASIC authentication method)
And a Wildfly standalone.xml configuration sample for security-domain configuration, using a standard provided Database login-module, not a custom one:
<security-domain name="securityDomain" cache-type="default">
<authentication>
<login-module code="Database" flag="required">
<module-option name="dsJndiName" value="java:/TestDS"/>
<module-option name="principalsQuery" value="select password from User where login = ? and (disabled is null or disabled = 0) and activated = 1"/>
<module-option name="rolesQuery" value="select name,'Roles' from Role r, User_Role ur, User u where u.login=? and u.id = ur.userId and r.id = ur.roleId"/>
<module-option name="hashAlgorithm" value="SHA-256"/>
<module-option name="hashEncoding" value="base64"/>
<module-option name="unauthenticatedIdentity" value="guest"/>
</login-module>
</authentication>
</security-domain>
Then, in your REST resource, you just have to use the #RolesAllowed or #PermitAll annotations to restrict access or not doing also authorization.
I don't redirect or forward my user to another page. So when the my SessionExpiredExceptionHandler (extends ExceptionHandlerWrapper) handles the ViewExireException. I want the user to stay on the same page and display a PrimeFaces Dialog. For notifying that the session has expired and that the user needs to login again (dialog based). I am use Servlet 3.1 functions to login/logout user and Basic/file for auth-method to map the users to different system roles.
What is happening now is that the View/page get refreshed after 2 min, but the session doesn't get invalidated. That only happens the second time when the page refreshes, after 4 min.
<session-config>
<session-timeout>2</session-timeout>
</session-config>
Edit:
Which is refreshed by the meta tag:
<meta http-equiv="refresh" content="#{session.maxInactiveInterval}" />
How can I make SessionExpiredExceptionHandlerinvalidate the session object (Servlet logout) when the Exceptions occur the first time, and how can I invoke a JavaScript (expireDlg.show()) on the client to display a PrimeFaces dialog ?
I have looked at some other threads but not found a viable solution.
Session time-out
SessionExpiredExceptionHandler
#Override
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof ViewExpiredException) {
ViewExpiredException vee = (ViewExpiredException) t;
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
requestMap.put("currentViewId", vee.getViewId());
nav.handleNavigation(fc, null, "Home");
fc.renderResponse();
} finally {
i.remove();
}
}
}
// At this point, the queue will not contain any ViewExpiredEvents.
// Therefore, let the parent handle them.
getWrapped().handle();
}
web.xml
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/home.xhtml</location>
</error-page>
How can I make SessionExpiredExceptionHandler invalidate the session object (Servlet logout) when the Exceptions occur the first time
The session is supposedly to be already invalidated/expired (otherwise a ViewExpiredException wouldn't be thrown at all), so I don't see how it's useful to manually invalidate/expire it yourself. But for the case that, you can invalidate it as follows:
externalContext.invalidateSession();
and how can I invoke a JavaScript (expireDlg.show()) on the client to display a PrimeFaces dialog ?
You can use the PrimeFaces RequestContext API to programmatically instruct PrimeFaces to execute some JS code on complete of ajax response.
RequestContext.getCurrentInstance().execute("expireDlg.show()");
Don't forget to remove the navigation handler block from the exception handler if you actually don't want to navigate.
This solution worked for my case. It seams that Primefaces (3.3) is swallowing the ExceptionQueuedEvent. There are no Exception to handle when my ViewExceptionHandler gets called. So instead I used the p:idleMonitor component with event listner. I also removed the meta refresh tag.
<p:idleMonitor timeout="#{(session.maxInactiveInterval-60)*1000}">
<p:ajax event="idle" process="#this" update="sessionMsg" listener="#{userController.userIdleSession()}" />
<p:ajax event="active" process="#this" update="sessionMsg" listener="#{userController.userActiveSession()}"/>
</p:idleMonitor>
One weird thing is if the timeoutis excatly the same as the web.xmlsession time-out parameter, the listener won't be invoked.
Bean functions
public void userIdleSession() {
if (!userIdleMsgVisable) {
userIdleMsgVisable = true;
JsfUtil.addWarningMessage(JsfUtil.getResourceMessage("session_expire_title"), JsfUtil.getResourceMessage("session_expire_content"));
}
}
public void userActiveSession() {
if (!userSessionDlgVisable) {
userSessionDlgVisable = true;
RequestContext.getCurrentInstance().execute("sessionExipreDlg.show()");
}
}
The dialog (sessionExipreDlg) called the redirect instead of using navigation handler to get new scope and refresh the page.
public void userInactiveRedirect() {
FacesContext fc = FacesContext.getCurrentInstance();
userIdleMsgVisable = false;
userSessionDlgVisable = false;
sessionUser = null;
HttpServletRequest request = (HttpServletRequest) fc.getExternalContext().getRequest();
JsfUtil.findBean("homeController", HomeController.class).clearCurrentValues();
try {
fc.getExternalContext().redirect(JsfUtil.getApplicationPath(request, false, null));
} catch (IOException ex) {
BeanUtil.severe(ex.getLocalizedMessage());
}
}
I'm trying to serve an iCalendar file (.ics) in my MVC application.
So far it's working fine. I have an iPhone subscribing to the URL for the calendar but now I need to serve a personalised calendar to each user.
When subscribing to the calendar on the iPhone I can enter a username and password, but I don't know how to access these in my MVC app.
Where can I find details of how the authentication works, and how to implement it?
It turns out that Basic Authentication is what is required. I half had it working but my IIS configuration got in the way. So, simply returning a 401 response when there is no Authorization header causes the client (e.g. iPhone) to require a username/password to subscribe to the calendar.
On the authorization of the request where there is an Authorization request header, the basic authentication can be processed, retrieving the username and password from the base 64 encoded string.
Here's some useful code for MVC:
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
var value = Encoding.ASCII.GetString(encodedDataAsBytes);
var username = value.Substring(0, value.IndexOf(':'));
var password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Calendar\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, my controller action looks like this:
[BasicAuthorize]
public ActionResult Calendar()
{
var userName = HttpContext.User.Identity.Name;
var appointments = GetAppointments(userName);
return new CalendarResult(appointments, "Appointments.ics");
}
I found this really helpful, but i hit a few problems during the development and i thought i would share some of them to help save other people some time.
I was looking to get data from my web application into the calendar for an android device and i was using discountasp as a hosting service.
The first problem i hit was that the validation did not work when uploaded to the server, stangely enough it was accepting my control panel login for discountasp but not my forms login.
The answer to this was to turn off Basic Authentication in IIS manager. This resolved the issue.
Secondly, the app i used to sync the calendar to the android device was called iCalSync2 - its a nice app and works well. But i found that it only worked properly when the file was delivered as a .ics (duh for some reason i put it as a .ical.. it must have been late) and i also had to choose the webcal option
Lastly i found i had to add webcal:// to the start of my url instead of http://
Also be careful as the code posted above ignores the roles input variable and always passes nothing so you might need to do some role based checks inside your calendar routine or modify the code above to process the roles variable.
We have an application where users with proxy rights need to be able to see links in an application.
For example, we might have:
<s:intercept-url pattern="/resourceManager.htm" access=" ROLE_ADMIN_GROUP, ROLE_PROXY"/>
If the user has the role of proxy, but not the admin role, I need to present them with a page telling them that they need to be in proxy mode to see this page. Additionally, I need to check the permissions of the user they proxy to, to verify they have the correct role.
We have multiple pages, so I'd like to like to do this logic in a filter, so we can apply the logic across the board.
I'm mocking this up in pseudo code while I continue to research.
class Filter
{
protected void doFilterHttp()
{
//proxy summary is session based object
if(proxySummary.isProxyMode())
{
user = proxySummary.getProxiedUser()
//here load user's authorities
//will have to look at ldap authorities populator, but I should be able to work this part out
}
if(user.getGrantedAuthorities.contains("Role_Proxy"))
{
//Is there any way to tell possible valid roles for a url?
if(url.getPossibleRoles() intersect user.getGrantedAuthorities().size == 1 &&
intersection.contains(Role_Proxy))
{ redirectToProxyPage(); }
}
}
What's the best way to get any metadata for the url I'm attempting to access?
If there is no way to get information on all allowable roles for a url, then I imagine I would have to do it at the page.
Would upgrading to Spring Security 3 give me any more flexibility?
I ended up creating a runAsManager implementation that ran as the proxy user if in proxy mode. Otherwise, if the user only had a proxy role for the link, they were redirected. The runAsManager only modified the authentication object when in proxy mode.
I've included snippets of each class, so as not to make the post too long.
RunAsProxy Snippet
public Authentication buildRunAs(Authentication authentication, Object object,
ConfigAttributeDefinition config) {
//probably need to do something to cache the proxied user's roles
if(proxySummary.isProxyMode())
{
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
String dn = proxySummary.getLoggedInUser();
String [] tmp = { "uid", "cn" };
DirContextOperations user = template.retrieveEntry(dn, tmp);
GrantedAuthority[] proxiedAuthorities = authoritiesPopulator.getGrantedAuthorities(user, user.getStringAttribute("cn").toString());
return new RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),
proxiedAuthorities, authentication.getClass());
}
return null;
}
Interceptor Code -> extends AbstractSecurityInterceptor implements Filter, Ordered
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//same code as from proxy security interceptor here ...
//config attributes are the roles assigned to a link
ConfigAttributeDefinition cad = ((DefaultFilterInvocationDefinitionSource)objectDefinitionSource).lookupAttributes(fi.getRequestUrl());
if(cad != null)
{
HashSet<String> configAttributes = new HashSet<String>();
for(Object ca: cad.getConfigAttributes())
{
configAttributes.add(((ConfigAttribute)ca).getAttribute());
}
SecurityContext sc = SecurityContextHolder.getContext();
HashSet<String> authorities = new HashSet<String>();
for(GrantedAuthority ga: sc.getAuthentication().getAuthorities())
{
authorities.add(ga.getAuthority());
}
//intersection and remaining available roles to determine
//if they just have the proxy role
authorities.retainAll(configAttributes);
if(authorities.size() == 1 && authorities.contains("ROLE_PROXY"))
{
//redirect to page telling them to proxy
((HttpServletResponse)fi.getResponse()).sendRedirect("jsp/doProxy.jsp");
}
//System.out.println(cad);
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
//other boilderplate code
}
Spring Setup
<bean id="proxySecurityInterceptor" class="org.springframework.security.intercept.web.ProxySecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="_accessManager"/>
<property name="proxySummary" ref="proxySummary" />
<property name="runAsManager" ref="runAsProxy" />
<property name="objectDefinitionSource">
<s:filter-invocation-definition-source>
<s:intercept-url pattern="/groupManager.htm*" access="ROLE_GLOBAL_ADMIN, ROLE_ADMIN_GROUP, ROLE_PROXY"/>
</s:filter-invocation-definition-source>
</property>
<s:custom-filter after="FILTER_SECURITY_INTERCEPTOR" />
</bean>