hi I have a j2ee application using Spring webflow and Spring Security. I want to implement an account lockout such that after three times of password failure the account wil be locked. How do I implement this.
Can you use an AuthenticationFailureHandler? This approach was suggested in the Acegi FAQ (see Common Problem #3).
That behavior belongs to the underline authentication provider. If you are using LDAP there is a Password Policy, the LdapAuthenticationProvider will throw an exception if the account is blocked.
If your current AuthenticationProvider doesn't have this functionality then subclass it.
You can use AuthenticationFailureHandler
public class MySimpleAuthenticationFailureHandler implements
AuthenticationFailureHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public MySimpleAuthenticationFailureHandler() {
super();
}
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
String message = "";
if(exception instanceof UsernameNotFoundException) {
message = "UsernameNotFoundException";
} else if(exception instanceof AuthenticationCredentialsNotFoundException) {
message = "AuthenticationCredentialsNotFoundException";
}else if(exception instanceof InsufficientAuthenticationException) {
message = "InsufficientAuthenticationException";
}else if(exception instanceof AccountExpiredException) {
message = "AccountExpiredException";
}else if(exception instanceof CredentialsExpiredException) {
message = "CredentialsExpiredException";
}else if(exception instanceof DisabledException) {
message = "DisabledException";
}else if(exception instanceof LockedException) {
message = "LockedException";
}else if(exception instanceof BadCredentialsException) {
message = "BadCredentialsException";
}else{
message = exception.getMessage();
}
final HttpSession session = request.getSession();
session.setAttribute("errorMessage", message);
redirectStrategy.sendRedirect(request, response, "/login?error="+message);
}
}
Related
I configured Vaadin 23 application with Spring Security and Keyclock. Everything works fine except the users are not redirect to the page where they initiated the login process. The user is always redirected to the home page.
This is a SecurityConfiguration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends VaadinWebSecurity {
private final ClientRegistrationRepository clientRegistrationRepository;
private final GrantedAuthoritiesMapper authoritiesMapper;
private final ProfileService profileService;
private String jwtAuthSecret;
SecurityConfiguration(#Value("${spring.security.jwt.auth.secret}") String jwtAuthSecret, ClientRegistrationRepository clientRegistrationRepository,
GrantedAuthoritiesMapper authoritiesMapper, ProfileService profileService) {
this.jwtAuthSecret = jwtAuthSecret;
this.clientRegistrationRepository = clientRegistrationRepository;
this.authoritiesMapper = authoritiesMapper;
this.profileService = profileService;
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
// Enable OAuth2 login
.oauth2Login(oauth2Login ->
oauth2Login
.clientRegistrationRepository(clientRegistrationRepository)
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint
.userAuthoritiesMapper(authoritiesMapper)
)
.loginPage("/login")
.successHandler(new KeycloakVaadinAuthenticationSuccessHandler(profileService))
)
// Configure logout
.logout(logout ->
logout
.logoutSuccessHandler(logoutSuccessHandler())
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
).sessionManagement(sessionManagement -> {
sessionManagement.sessionConcurrency(concurrency -> {
concurrency.maximumSessions(-1);
concurrency.sessionRegistry(sessionRegistry());
final var expiredStrategy = new UidlExpiredSessionStrategy();
concurrency.expiredSessionStrategy(expiredStrategy);
});
});
setStatelessAuthentication(http, new SecretKeySpec(Base64.getDecoder().decode(jwtAuthSecret), JwsAlgorithms.HS256), "com.example");
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
#Primary
public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) {
return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak");
}
private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() {
var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return logoutSuccessHandler;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.ignoring().antMatchers("/session-expired", "/images/*", "/login", "/favicon.ico");
}
#Bean
public PolicyFactory htmlSanitizer() {
return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
}
}
How to properly redirect user to the original page?
UPDATED
public class KeycloakVaadinAuthenticationSuccessHandler extends VaadinSavedRequestAwareAuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(KeycloakVaadinAuthenticationSuccessHandler.class);
private final ServiceFacade serviceFacade;
public KeycloakVaadinAuthenticationSuccessHandler(ServiceFacade serviceFacade) {
this.serviceFacade = serviceFacade;
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication == null || !(authentication instanceof OAuth2AuthenticationToken)) {
String message = String.format("Authentication is null or not an instance of OAuth2AuthenticationToken: %s", authentication);
logger.error(message);
throw new IllegalStateException(message);
}
Collection<VaadinSession> vaadinSessions = VaadinSession.getAllSessions(request.getSession());
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
String keycloakSessionId = (String) token.getPrincipal().getAttributes().get("sid");
serviceFacade.getProfileService().createUserWithProfileIfNotExists((OAuth2AuthenticationToken) authentication, (user, profile, principalUserUuid) -> {
try {
if (CollectionUtils.isNotEmpty(vaadinSessions)) {
for (VaadinSession vaadinSession : vaadinSessions) {
if (vaadinSession.getService() != null) {
vaadinSession.access(() -> {
vaadinSession.setAttribute(UserInfo.SUB_PROPERTY, user.getUuid());
vaadinSession.setAttribute(UserInfo.KEYCLOAK_SESSION_ID, keycloakSessionId);
});
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
}
});
super.onAuthenticationSuccess(request, response, authentication);
}
The success handler is the one taking care of redirecting to the original view. You are overriding that with your own version so it will not work out of the box. There is nowadays a setOAuth2LoginPage helper in VaadinWebSecurity that will set up the correct success handler.
I need to integrate on Liferay 6.2 GA6 a SSO from a web application that provide info by oAuth
A native support doesn't exist.
My problem is to create the automatic login on Liferay (after the user creation or if the user already exists). Any help ?
You have to create a hook where you create an AutoLogin class that extends BaseAutoLogin. Read the oAuth documentation and write a login logic in that hook, then set it in auto.login.hooks property in portal-ext.properties(properties reference). Then you will have to create a filter that extends BasePortalFilter and implemets processFilter method. You can model on CASFilter and CASAutologin
override portal.properties adding
auto.login.hooks=com.yourpackage.hook.MyAutoLogin
Create the class:
package com.yourpackage.hook;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.model.User;
import com.liferay.portal.security.auth.AutoLogin;
import com.liferay.portal.security.auth.AutoLoginException;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.util.PortalUtil;
//based on example
// https://bitbucket.org/brandizzi/liferay-examples/src/a41d71eba8f2fb2d4272a3ce8f393e77cec41d60/unsafe-login-hook/docroot/WEB-INF/src/br/brandizzi/adam/liferay/unsecure/UnsecureAutoLogin.java?at=default&fileviewer=file-view-default
public class MyAutoLogin implements AutoLogin {
#Override
public String[] login(HttpServletRequest request,HttpServletResponse response) throws AutoLoginException {
HttpSession session = request.getSession();
String emailAddress = (String) session.getAttribute("LIFERAY_SHARED_EMAIL");
if (emailAddress == null || emailAddress.isEmpty())
return null;
long companyId = PortalUtil.getCompanyId(request);
User user = null;
try {
user = UserLocalServiceUtil.getUserByEmailAddress(companyId, emailAddress);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
String redirect = ParamUtil.getString(request, "redirect");
if (Validator.isNotNull(redirect)) {
request.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT_AND_CONTINUE,PortalUtil.escapeRedirect(redirect));
}
String[] credentials = new String[3];
credentials[0] = String.valueOf(user.getUserId());
credentials[1] = user.getPassword();
credentials[2] = String.valueOf(user.isPasswordEncrypted());
// credentials[2] = Boolean.FALSE.toString();
return credentials;
}
#Override
public String[] handleException(HttpServletRequest arg0,
HttpServletResponse arg1, Exception arg2)
throws AutoLoginException {
System.out.println("AutoLogin handleException ");
return null;
}
}
create an other class with the static methods:
public static JSONObject doSSO(String firstname, String surname, String email, String username,String accessToken, ActionRequest actionRequest, ActionResponse actionResponse){
JSONObject jsonResp = JSONFactoryUtil.createJSONObject();
//Get default Liferay company
String webId = new String("liferay.com");
Company company = null;
try {
company = CompanyLocalServiceUtil.getCompanyByWebId(webId);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
System.out.println("email "+email);
User currentUser = null;
try {
currentUser = UserLocalServiceUtil.getUserByEmailAddress(company.getCompanyId(), email);
} catch (SystemException | PortalException e) {
System.out.println("User to create");
}
if (Validator.isNull(currentUser)){
long newUserId = 0;
try {
jsonResp = addNewUser( firstname, surname, email, username );
} catch (Exception e) {
e.printStackTrace();
}
String newUserIdS = jsonResp.getString("newUserId");
newUserId = Long.valueOf(newUserIdS);
try {
currentUser = UserLocalServiceUtil.fetchUser(newUserId);
} catch (SystemException e) {
e.printStackTrace();
}
notifyAuthorAboutInvited(email, currentUser);
}
setExistingUserOnSession( actionRequest,currentUser, accessToken);
//Login the user
HttpServletRequest request = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(actionRequest));
HttpServletResponse response = PortalUtil.getHttpServletResponse(actionResponse);
MyAutoLogin myLogin = new MyAutoLogin();
try {
myLogin.login(request, response);
jsonResp.put("message","OK - User logged on Liferay");
} catch (AutoLoginException e1) {
e1.printStackTrace();
}
//set Token on customfield
//remember to set permission guest to view and update
ServiceContext serviceContext = null;
try {
serviceContext = ServiceContextFactory.getInstance(User.class.getName(), actionRequest);
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
Map<String,Serializable> expandoBridgeAttributes = new HashMap<String, Serializable>();
expandoBridgeAttributes.put("token", accessToken);
serviceContext.setExpandoBridgeAttributes(expandoBridgeAttributes);
currentUser.setExpandoBridgeAttributes(serviceContext);
try {
UserLocalServiceUtil.updateUser(currentUser);
} catch (SystemException e) {
e.printStackTrace();
}
String userToken =currentUser.getExpandoBridge().getAttribute("token").toString();
//System.out.println("doSSO accessToken dopo "+userToken);
return jsonResp;
}
and:
private static void setExistingUserOnSession(ActionRequest actionRequest,User user, String accessToken) {
HttpServletRequest req = PortalUtil.getHttpServletRequest(actionRequest);
HttpSession session = req.getSession();
session.setAttribute("LIFERAY_SHARED_EMAIL", user.getEmailAddress());
}
I want to redirect a user upon a successful login based on his roles. I am following this example but the method never executes(print does not happen).
My resources.groovy:
beans = {
authenticationSuccessHandler(UrlRedirectEventListener)
{
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
defaultUrl= "/"
adminUrl = "/admin/"
userUrl = "/user/"
}
}
And my UrlRedirectEventListener
class UrlRedirectEventListener
extends SavedRequestAwareAuthenticationSuccessHandler
{
#Override
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
boolean hasBoth = SpringSecurityUtils.ifAllGranted("ROLE_USER,ROLE_ADMIN");
boolean hasUser = SpringSecurityUtils.ifAnyGranted("ROLE_USER");
boolean hasAdmin = SpringSecurityUtils.ifAnyGranted("ROLE_ADMIN");
println("hasBoth:"+hasBoth+ " hasUser:"+hasUser+ " hasAdmin:"+hasAdmin)
if( hasBoth ){
return defaultLoginUrl;
}else if ( hasUser ){
return userUrl ;
}else if ( hasAdmin ){
return adminUrl ;
}else{
return super.determineTargetUrl(request, response);
}
}
private String defaultLoginUrl;
private String mmrLoginUrl;
private String pubApiLoginUrl;
...setters for urls
}
What am I missing?
I am using Grails 2.0.4 and Spring Security Plugin 2 RC2.0
Thanks!
Edit:
I also have this controller to test that my bean is created, which shows up correctly.
class TestController {
AuthenticationSuccessHandler authenticationSuccessHandler
def index()
{
render( authenticationSuccessHandler )
}
}
After looking at the source, I figured it out. The problem was that SavedRequestAwareAuthenticationSuccessHandler's method onAuthenticationSuccess would never reach the overridden method determineTargetUrl with the simple login scheme I was using( the default that is generated by the plugin ). I had to actually override onAuthenticationSuccess instead and put my logic there.
The source in question:
SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
My updated UrlRedirectEventListener, with most copy-pasted from SavedRequestAwareAuthenticationSuccessHandler:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
def targetUrl = savedRequest.getRedirectUrl();
if( targetUrl != null && targetUrl != "" && targetUrl.endsWith("/myApp/") ) // I only want to differently handle "/". If a user hits /user/, or any other url, attempt to send him there. Spring Security will deny him access based on his privileges if necessary.
targetUrl = this.determineTargetUrl( request, response ) // This is the method defined above. Only thing changed there is I removed the `super` call
clearAuthenticationAttributes(request);
logger.info("Redirecting to Url: " + targetUrl);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
I have tried every way imaginable. Disabled SSO in Glassfish. Put session.invalidate() in a jsp page and redirected. Did a request.invalidate in a jsf backing bean. What should I do? Thanks.
EDIT
This works.
private String email;
public ViewLines() {
}
#PostConstruct
public void onLoad() {
Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
email = principal.getName();
if (email != null) {
User user = userService.findUserbyEmail(email);
for (Acl acl : user.getAclList()) {
if (acl.getAccess().equals("allow")) {
digPackageLines(acl.getTreeId());
}
}
} else {
FacesUtils.addFatalMessage("Couldn't find user information from login!");
}
}
#WebServlet(value = "/j_security_logout")
public class LogoutServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
response.sendRedirect("index.jsf");
}
}
I have a MVC app where I have a User class and the user can also impersonate another user(Admin users only).
So I have this code below that authenticates the request and instantiates my version of a User class.
It then tries to get the impersonated user from the Session object but Session is not available in this method in the global.asax.
Hope this makes sense.
How else could I do this?
My question I guess is at what point in the global.asax methods do you get access to Session object for each request?
protected void Application_OnAuthenticateRequest(object sender, EventArgs e)
{
IMylesterService service = ObjectFactory.GetInstance<IMylesterService>();
if (Context.User != null)
{
if (Context.User.Identity.IsAuthenticated)
{
User user = service.GetUser(Context.User.Identity.Name);
if (user == null)
throw new ApplicationException("Context.user.Identity.name is not a recognized user");
User impersonatedUser = (User)this.Session["ImpersonatedUser"];
if (impersonatedUser == null)
user.ImpersonatedUser = user;
else
user.ImpersonatedUser = impersonatedUser;
System.Threading.Thread.CurrentPrincipal = Context.User = user;
return;
}
}
User guest = service.GetGuestUser();
guest.ImpersonatedUser = guest;
System.Threading.Thread.CurrentPrincipal = Context.User = guest;
}
Try creating an authorization filter:
public class CustomAuthorizationFilter : AuthorizeAttribute
{
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// perform your authorization here
// full access to HttpContext and session
}
}
You can then apply this attribute to your controllers. Ideally you'd have a base controller that all other controllers inherit from and you could apply the attribute at the class level on that controller. Then all of your requests would be authorized and apply the impersonation as you have coded above.
Session will not be available during AuthenticateRequest: What you will need to do is tag the required information to the Identity.userData; so for example if you where using forms authentication do the following:
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Context.User != null)
{
if (Context.User.Identity.IsAuthenticated)
{
// retrieve the value
var id = (FormsIdentity)Context.User.Identity;
var myvalue = id.Ticket.UserData; // "Here you are"
}
}
}
For sign in using forms you will need to write a custom cookie:
MVC -> class FormsAuthenticationService : IFormsAuthenticationService
public static void SetAuthenticationCookie(HttpContextBase context, FormsAuthenticationTicket ticket)
{
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName)
{
Value = FormsAuthentication.Encrypt(ticket),
Secure = FormsAuthentication.RequireSSL,
Domain = FormsAuthentication.CookieDomain,
HttpOnly = true,
Expires = DateTime.Now.AddMinutes(15)
};
if (!context.Request.IsSecureConnection && FormsAuthentication.RequireSSL)
{
throw new HttpException("Ticket requires SSL.");
}
context.Response.Cookies.Add(cookie);
}
public static FormsAuthenticationTicket CreateTicket(HttpContextBase context, string emailAddress, string userData, bool persist)
{
return new FormsAuthenticationTicket(1, emailAddress, DateTime.Now, DateTime.Now.AddMinutes(15), persist, userData, FormsAuthentication.FormsCookiePath);
}
Finally in SignIn you would now create the required ticket by calling CreateTicket(...), and then you would write it out by SetAuthenticationCookie(...).
public void SignIn(string userName, string password)
{
if(CheckUserValid(userName,password, out string email))
{
var ticket = CreateTicket(email, "Here you are", true);
SetAuthenticationCookie(HttpContext.Current.Base(), ticket);
}
}
I have never used it, but assume that session state is assigned during the AcquireRequestState:
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
I had this same issue of needing to access session in global.asax and finally solved it by moving my code into the AcquireRequestState handler, which happens after the authentication is passed.
protected void Application_AcquireRequestState(Object sender, EventArgs e)
{
if (Request.IsAuthenticated && Context.Session != null)
{
// access Context.Session
}
}
This fires a lot and the current context does not always have a valid session object, hence the check.
EDIT: Had to add the check for IsAuthenticated too -- was getting a null error when logged out. Works great now.