I can't seem to find an example to load the [users] AND [urls] from my JPA objects.
I want to use shiro.ini for [main] section only.
The source code of what I achieved so far is this:
Unable to #Inject my DAO in a Custom Apache Shiro AuthorizingRealm
Is there any example where [users] (user/pass) AND [urls] (roles, permissions) are FULLY loaded from database? I can't seem to find that anywhere. I'm looking for it for 1 week now.
After some long research, the "best" solution I came up with was this:
shiro.ini
[main]
jsfFilter = com.test.security.CustomAuthorizationFilter
jsfFilter.loginUrl = /login.jsf
[urls]
/** = jsfFilter
// You can add the methods to filter Ajax requests described by BalusC inside this filter.
CustomAuthorizationFilter.java
public class CustomAuthorizationFilter extends AuthorizationFilter {
#Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (!httpRequest.getRequestURI().startsWith(httpRequest.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) {
Subject subject = SecurityUtils.getSubject();
AuthenticatingSecurityManager authenticatingSecurityManager = ((AuthenticatingSecurityManager) SecurityUtils.getSecurityManager());
PrincipalCollection principals = subject.getPrincipals();
JPARealm jpaRealm = (JPARealm) authenticatingSecurityManager.getRealms().iterator().next();
AuthorizationInfo authorizationInfo = jpaRealm.getAuthorizationInfo(principals);
for (String permission : authorizationInfo.getStringPermissions()) {
if (pathsMatch(permission, request)) {
return true;
}
}
} else {
return true;
}
return false;
}
}
The pathsMatch(permission, request) method will try to validate/compare the received string permission with the path the user is trying to access.
This filter relies on ALWAYS having an authenticated user.
If the subject.getPrincipal() is null, more coding is necessary.
If anyone needs the whole code, let me know.
Related
I want to retrieve username using EJBContext.getCallerPrincipal()
public class GlobalInterceptor {
#EJB
private AuditLogsEJB auditLogsEJB;
#Resource
private EJBContext context;
#AroundInvoke
public Object auditLog(InvocationContext invocationContext) throws Exception
{
System.out.println(context.getCallerPrincipal()); //it outputs "ANONYMOUS"
}
}
However I don't have any security realm, I don't have any <security-role> or <group-name> defined because the username/password is sent to a remote application to validate. But for convenience sake I still want to use the Principal class to get username and role.
Is there a way to programmatically set Principal(username and role) in JSF managed bean or any EJB, so that the interceptor can retrieve it? e.g.:
String role = authenticationWSPort.validate(username, password);
if(role != null)
{
Principal p = new Principal(username);
p.getRoles.add(role);
FacesContext.getCurrentInstance().blah-blah.blah-blah.addPrincipal(p);
//I am looking for something similar to the 3 lines above
}
else
// invalid username and password
Obviously the correct way to do this is by setting up a realm, and using that.
Perhaps you could create & configure your own Realm implementation that would let you create users on the fly, prior to invoking request.login(username, password) in a servlet.
Or.
If you -really- want to futz with the underlying Request, it's possible, but it's application-server specific, and goes something like this, assuming you're in a tomcat (catalina) based container:
if (request instanceof RequestFacade) {
Request unwrapped = null;
RequestFacade rf = RequestFacade.class.cast(request);
try {
Field requestField = rf.getClass().getDeclaredField("request");
requestField.setAccessible(true);
unwrapped = Request.class.cast(requestField.get(rf));
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
unwrapped = null;
}
if (unwrapped != null) {
unwrapped.setUserPrincipal(... your principal construction here ...);
}
}
I am in a Jboss AS 7 environment. My application's /admIn/* path is protected by a security-constraint which requires form based authentication. Security domain is database backed.
It's ok but now I want to display a "good morning " in each page's header.
I'm looking for some sort of getLoggedUsername() or getPrincipal() function but I can't find it.
Please post a reference to the official docs if any.
Thank you.
You should be able to use JAAS. Which is what JBoss 7 ought to be using.
The calling principal will be stored in a SessionContext which you can obtain by telling JBoss it's a resource.
#Resource
private SessionContext context;
public void myAwesomeMethod() {
String currentUser = context.getCallerPrincipal().getName();
}
If for some reason the Injection doesn't work on a Stateless bean, you can look up the EJBContext direct.
#Stateless
public class HelloBean implements com.foo.ejb.HelloRemote {
public void hello() {
try {
InitialContext ic = new InitialContext();
SessionContext sctxLookup =
(SessionContext) ic.lookup("java:comp/EJBContext");
System.out.println("look up EJBContext by standard name: " + sctxLookup);
} catch (NamingException ex) {
throw new IllegalStateException(ex);
}
}
}
This snippet was obtained from 4 ways to obtain EJBContext.
Is anyone aware of a method to dynamically combine/minify all the h:outputStylesheet resources and then combine/minify all h:outputScript resources in the render phase? The comined/minified resource would probably need to be cached with a key based on the combined resource String or something to avoid excessive processing.
If this feature doesn't exist I'd like to work on it. Does anyone have ideas on the best way to implement something like this. A Servlet filter would work I suppose but the filter would have to do more work than necessary -- basically examining the whole rendered output and replacing matches. Implementing something in the render phase seems like it would work better as all of the static resources are available without having to parse the entire output.
Thanks for any suggestions!
Edit: To show that I'm not lazy and will really work on this with some guidance, here is a stub that captures Script Resources name/library and then removes them from the view. As you can see I have some questions about what to do next ... should I make http requests and get the resources to combine, then combine them and save them to the resource cache?
package com.davemaple.jsf.listener;
import java.util.ArrayList;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.event.PreRenderViewEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import org.apache.log4j.Logger;
/**
* A Listener that combines CSS/Javascript Resources
*
* #author David Maple<d#davemaple.com>
*
*/
public class ResourceComboListener implements PhaseListener, SystemEventListener {
private static final long serialVersionUID = -8430945481069344353L;
private static final Logger LOGGER = Logger.getLogger(ResourceComboListener.class);
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
/*
* (non-Javadoc)
* #see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
public void afterPhase(PhaseEvent event) {
FacesContext.getCurrentInstance().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class, this);
}
/*
* (non-Javadoc)
* #see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
public void beforePhase(PhaseEvent event) {
//nothing here
}
/*
* (non-Javadoc)
* #see javax.faces.event.SystemEventListener#isListenerForSource(java.lang.Object)
*/
public boolean isListenerForSource(Object source) {
return (source instanceof UIViewRoot);
}
/*
* (non-Javadoc)
* #see javax.faces.event.SystemEventListener#processEvent(javax.faces.event.SystemEvent)
*/
public void processEvent(SystemEvent event) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = context.getViewRoot();
List<UIComponent> scriptsToRemove = new ArrayList<UIComponent>();
if (!context.isPostback()) {
for (UIComponent component : viewRoot.getComponentResources(context, "head")) {
if (component.getClass().equals(UIOutput.class)) {
UIOutput uiOutput = (UIOutput) component;
if (uiOutput.getRendererType().equals("javax.faces.resource.Script")) {
String library = uiOutput.getAttributes().get("library").toString();
String name = uiOutput.getAttributes().get("name").toString();
// make https requests to get the resources?
// combine then and save to resource cache?
// insert new UIOutput script?
scriptsToRemove.add(component);
}
}
}
for (UIComponent component : scriptsToRemove) {
viewRoot.getComponentResources(context, "head").remove(component);
}
}
}
}
This answer doesn't cover minifying and compression. Minifying of individual CSS/JS resources is better to be delegated to build scripts like YUI Compressor Ant task. Manually doing it on every request is too expensive. Compression (I assume you mean GZIP?) is better to be delegated to the servlet container you're using. Manually doing it is overcomplicated. On Tomcat for example it's a matter of adding a compression="on" attribute to the <Connector> element in /conf/server.xml.
The SystemEventListener is already a good first step (apart from some PhaseListener unnecessity). Next, you'd need to implement a custom ResourceHandler and Resource. That part is not exactly trivial. You'd need to reinvent pretty a lot if you want to be JSF implementation independent.
First, in your SystemEventListener, you'd like to create new UIOutput component representing the combined resource so that you can add it using UIViewRoot#addComponentResource(). You need to set its library attribute to something unique which is understood by your custom resource handler. You need to store the combined resources in an application wide variable along an unique name based on the combination of the resources (a MD5 hash maybe?) and then set this key as name attribute of the component. Storing as an application wide variable has a caching advantage for both the server and the client.
Something like this:
String combinedResourceName = CombinedResourceInfo.createAndPutInCacheIfAbsent(resourceNames);
UIOutput component = new UIOutput();
component.setRendererType(rendererType);
component.getAttributes().put(ATTRIBUTE_RESOURCE_LIBRARY, CombinedResourceHandler.RESOURCE_LIBRARY);
component.getAttributes().put(ATTRIBUTE_RESOURCE_NAME, combinedResourceName + extension);
context.getViewRoot().addComponentResource(context, component, TARGET_HEAD);
Then, in your custom ResourceHandler implementation, you'd need to implement the createResource() method accordingly to create a custom Resource implementation whenever the library matches the desired value:
#Override
public Resource createResource(String resourceName, String libraryName) {
if (RESOURCE_LIBRARY.equals(libraryName)) {
return new CombinedResource(resourceName);
} else {
return super.createResource(resourceName, libraryName);
}
}
The constructor of the custom Resource implementation should grab the combined resource info based on the name:
public CombinedResource(String name) {
setResourceName(name);
setLibraryName(CombinedResourceHandler.RESOURCE_LIBRARY);
setContentType(FacesContext.getCurrentInstance().getExternalContext().getMimeType(name));
this.info = CombinedResourceInfo.getFromCache(name.split("\\.", 2)[0]);
}
This custom Resource implementation must provide a proper getRequestPath() method returning an URI which will then be included in the rendered <script> or <link> element:
#Override
public String getRequestPath() {
FacesContext context = FacesContext.getCurrentInstance();
String path = ResourceHandler.RESOURCE_IDENTIFIER + "/" + getResourceName();
String mapping = getFacesMapping();
path = isPrefixMapping(mapping) ? (mapping + path) : (path + mapping);
return context.getExternalContext().getRequestContextPath()
+ path + "?ln=" + CombinedResourceHandler.RESOURCE_LIBRARY;
}
Now, the HTML rendering part should be fine. It'll look something like this:
<link type="text/css" rel="stylesheet" href="/playground/javax.faces.resource/dd08b105bf94e3a2b6dbbdd3ac7fc3f5.css.xhtml?ln=combined.resource" />
<script type="text/javascript" src="/playground/javax.faces.resource/2886165007ccd8fb65771b75d865f720.js.xhtml?ln=combined.resource"></script>
Next, you have to intercept on combined resource requests made by the browser. That's the hardest part. First, in your custom ResourceHandler implementation, you need to implement the handleResourceRequest() method accordingly:
#Override
public void handleResourceRequest(FacesContext context) throws IOException {
if (RESOURCE_LIBRARY.equals(context.getExternalContext().getRequestParameterMap().get("ln"))) {
streamResource(context, new CombinedResource(getCombinedResourceName(context)));
} else {
super.handleResourceRequest(context);
}
}
Then you have to do the whole lot of work of implementing the other methods of the custom Resource implementation accordingly such as getResponseHeaders() which should return proper caching headers, getInputStream() which should return the InputStreams of the combined resources in a single InputStream and userAgentNeedsUpdate() which should respond properly on caching related requests.
#Override
public Map<String, String> getResponseHeaders() {
Map<String, String> responseHeaders = new HashMap<String, String>(3);
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
sdf.setTimeZone(TIMEZONE_GMT);
responseHeaders.put(HEADER_LAST_MODIFIED, sdf.format(new Date(info.getLastModified())));
responseHeaders.put(HEADER_EXPIRES, sdf.format(new Date(System.currentTimeMillis() + info.getMaxAge())));
responseHeaders.put(HEADER_ETAG, String.format(FORMAT_ETAG, info.getContentLength(), info.getLastModified()));
return responseHeaders;
}
#Override
public InputStream getInputStream() throws IOException {
return new CombinedResourceInputStream(info.getResources());
}
#Override
public boolean userAgentNeedsUpdate(FacesContext context) {
String ifModifiedSince = context.getExternalContext().getRequestHeaderMap().get(HEADER_IF_MODIFIED_SINCE);
if (ifModifiedSince != null) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
try {
info.reload();
return info.getLastModified() > sdf.parse(ifModifiedSince).getTime();
} catch (ParseException ignore) {
return true;
}
}
return true;
}
I've here a complete working proof of concept, but it's too much of code to post as a SO answer. The above was just a partial to help you in the right direction. I assume that the missing method/variable/constant declarations are self-explaining enough to write your own, otherwise let me know.
Update: as per the comments, here's how you can collect resources in CombinedResourceInfo:
private synchronized void loadResources(boolean forceReload) {
if (!forceReload && resources != null) {
return;
}
FacesContext context = FacesContext.getCurrentInstance();
ResourceHandler handler = context.getApplication().getResourceHandler();
resources = new LinkedHashSet<Resource>();
contentLength = 0;
lastModified = 0;
for (Entry<String, Set<String>> entry : resourceNames.entrySet()) {
String libraryName = entry.getKey();
for (String resourceName : entry.getValue()) {
Resource resource = handler.createResource(resourceName, libraryName);
resources.add(resource);
try {
URLConnection connection = resource.getURL().openConnection();
contentLength += connection.getContentLength();
long lastModified = connection.getLastModified();
if (lastModified > this.lastModified) {
this.lastModified = lastModified;
}
} catch (IOException ignore) {
// Can't and shouldn't handle it here anyway.
}
}
}
}
(the above method is called by reload() method and by getters depending on one of the properties which are to be set)
And here's how the CombinedResourceInputStream look like:
final class CombinedResourceInputStream extends InputStream {
private List<InputStream> streams;
private Iterator<InputStream> streamIterator;
private InputStream currentStream;
public CombinedResourceInputStream(Set<Resource> resources) throws IOException {
streams = new ArrayList<InputStream>();
for (Resource resource : resources) {
streams.add(resource.getInputStream());
}
streamIterator = streams.iterator();
streamIterator.hasNext(); // We assume it to be always true; CombinedResourceInfo won't be created anyway if it's empty.
currentStream = streamIterator.next();
}
#Override
public int read() throws IOException {
int read = -1;
while ((read = currentStream.read()) == -1) {
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
} else {
break;
}
}
return read;
}
#Override
public void close() throws IOException {
IOException caught = null;
for (InputStream stream : streams) {
try {
stream.close();
} catch (IOException e) {
if (caught == null) {
caught = e; // Don't throw it yet. We have to continue closing all other streams.
}
}
}
if (caught != null) {
throw caught;
}
}
}
Update 2: a concrete and reuseable solution is available in OmniFaces. See also CombinedResourceHandler showcase page and API documentation for more detail.
You may want to evaluate JAWR before implementing your own solution. I've used it in couple of projects and it was a big success. It used in JSF 1.2 projects but I think it will be easy to extend it to work with JSF 2.0. Just give it a try.
Omnifaces provided CombinedResourceHandler is an excellent utility, but I also love to share about this excellent maven plugin:- resources-optimizer-maven-plugin that can be used to minify/compress js/css files &/or aggregate them into fewer resources during the build time & not dynamically during runtime which makes it a more performant solution, I believe.
Also have a look at this excellent library as well:- webutilities
I have an other solution for JSF 2. Might also rok with JSF 1, but i do not know JSF 1 so i can not say. The Idea works mainly with components from h:head and works also for stylesheets. The result
is always one JavaScript (or Stylesheet) file for a page! It is hard for me to describe but i try.
I overload the standard JSF ScriptRenderer (or StylesheetRenderer) and configure the renderer
for the h:outputScript component in the faces-config.xml.
The new Renderer will now not write anymore the script-Tag but it will collect all resources
in a list. So first resource to be rendered will be first item in the list, the next follows
and so on. After last h:outputScript component ist rendered, you have to render 1 script-Tag
for the JavaScript file on this page. I make this by overloading the h:head renderer.
Now comes the idea:
I register an filter! The filter will look for this 1 script-Tag request. When this request comes,
i will get the list of resources for this page. Now i can fill the response from the list of
resources. The order will be correct, because the JSF rendering put the resources in correct order
into the list. After response is filled, the list should be cleared. Also you can do more
optimizations because you have the code in the filter....
I have code that works superb. My code also can handle browser caching and dynamic script rendering.
If anybody is interested i can share the code.
I've tested the default security containers in Glassfish 3.0.1 and come to the conclusion that I won't spend any more time on that. Instead I want to control the verification myself. But I need some guidance to get me on right track.
At the moment I have a UserBean that has a login/logout function (see below). And I don't want to use the *j_security_check* built in container, but use core JSF 2.0.
My questions are;
Do I need a ServletFilter to redirect traffic if the user is not logged in (if accessing certain folders)?
How do I store User Pricipals after the user successfully logged in ?
Appreciate any help or link to a example, greetings Chris.
PS. Excuse me for clustering two questions together
#ManagedBean
#SessionScoped
public class UserBean {
private AuthenticateUser authenticateUser;
...
public String login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
JsfUtil.log("Username : " +authenticateUser.getUserName());
JsfUtil.log("Password : " +authenticateUser.getPassword());
AuthenticateUser authRequest = authenticationFacade.find(authenticateUser);
try {
if(!authRequest.equals(authenticateUser))
return "/loginError";
request.login(authenticateUser.getUserName(), authenticateUser.getPassword());
return "";
} catch(ServletException e){
JsfUtil.addErrorMessage(e, "Incorrect username or password, please try again.");
return "/loginError";
}
...
public String logOut() {
String result = "/index?faces-redirect=true";
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
request.logout();
} catch (ServletException e) {
JsfUtil.log("Failed to logout user!" +e.getRootCause().toString());
result = "/loginError?faces-redirect=true";
}
return result;
}
When you want to utilize request.login(), then you should really have configured a Realm in the container which represents the user database. But you seem to have replaced the Realm by some AuthenticationFacade. In this case, the request.login() is not useful for you.
You need to just put the user in the session scope and intercept on that. Here's a kickoff example:
#ManagedBean
#SessionScoped
public class UserManager {
#EJB
private UserService userService;
private String username;
private String password;
private User current;
public String login() {
current = userService.find(username, password);
if (current == null) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Unknown login, try again"));
return null;
} else {
return "userhome?faces-redirect=true";
}
}
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "index?faces-redirect=true";
}
public boolean isLoggedIn() {
return current != null;
}
// Getters/setters (but do NOT provide a setter for current!)
}
When taking authentication in hands like this, then you definitely need a filter to restrict access. When using container managed security you would typically specify it as <url-pattern> of <security-constraint> for this. But without it, you've to take it in your hands. It's good to know that JSF managed beans are keyed by their managed bean name in any scope.
UserManager userManager = ((HttpServletRequest) request).getSession().getAttribute("userManager");
if (userManager == null || !userManager.isLoggedIn()) {
((HttpServletResponse) response).sendRedirect("login.xhtml");
} else {
chain.doFilter(request, response);
}
Map the above filter on the desired URL-pattern.
When you still want to reconsider using container managed authentication, then the following related answers may be useful:
Java EE Login Page Problem (and Configuring Realm in Glassfish)
Performing user authentication in Java EE / JSF using j_security_check
Be aware if you are if you are using JDBC realm security. There are some fixed/expected words in the fields where you configure the realm in the Glassfish admin console.
In the JAAS Context: filed, you have to type: jdbcRealm. This keyword makes the security container use the expected JDBC realm. If you type something else, it won't work.
Here is good example, done by Gordan Jugo; Netbeans/Glassfish JDBC Security Realm
Is there some configuration or available module in Spring Security to limit login attempts (ideally, I'd like to have an increasing wait time between subsequent failed attempts)? If not, which part of the API should be used for this?
From Spring 4.2 upwards annotation based event listeners are available:
#Component
public class AuthenticationEventListener {
#EventListener
public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
// update the failed login count for the user
// ...
}
}
Implement an AuthenticationFailureHandler that updates a count/time in the DB. I wouldn't count on using the session because the attacker is not going to be sending cookies anyway.
I recently implemented a similar functionality to monitor login failures using JMX. Please see the code in my answer to question Publish JMX notifications in using Spring without NotificationPublisherAware. An aspect on the authenticate method of authentication provider updates MBean and works with a notification listener (code not shown in that question) to block user and IP, send alert emails and even suspend the login if failures exceed a threshold.
Edit
Similar to my answer to question Spring security 3 : Save informations about authentification in database, I think that capturing an authentication failure event (as opposed to customizing a handler) and storing information in database will also work and it will keep the code decoupled as well.
As suggested by Rob Winch in http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security, I just subclassed DaoAuthenticationProvider (which could also have been done using an aspect as Ritesh suggests) to limit the number of failed logins, but you could also assert pre-conditions as well:
public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Could assert pre-conditions here, e.g. rate-limiting
// and throw a custom AuthenticationException if necessary
try {
return super.authenticate(authentication);
} catch (BadCredentialsException e) {
// Will throw a custom exception if too many failed logins have occurred
userService.recordLoginFailure(authentication);
throw e;
}
}
}
In Spring config XML, simply reference this bean:
<beans id="authenticationProvider"
class="mypackage.LimitingDaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService"
p:passwordEncoder-ref="passwordEncoder"/>
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
Note that I think that solutions which rely on accessing an AuthenticationException's authentication or extraInformation properties (such as implementing an AuthenticationFailureHandler) should probably not be used because those properties are now deprecated (in Spring Security 3.1 at least).
You could also use a service which implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> to update the record in DB.
See spring application events.
Here is my implementation, hope help.
Create a table to store any invalid login attempts.
If invalid attempts > max allowed, set UserDetail.accountNonLocked to false
Spring Security will handle the "lock process" for you. (refer to AbstractUserDetailsAuthenticationProvider)
Last, extends DaoAuthenticationProvider, and integrate the logic inside.
#Component("authenticationProvider")
public class YourAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
UserAttemptsDao userAttemptsDao;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
Authentication auth = super.authenticate(authentication);
//if corrent password, reset the user_attempts
userAttemptsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//invalid login, update user_attempts, set attempts+1
userAttemptsDao.updateFailAttempts(authentication.getName());
throw e;
}
}
}
For full source code and implementation, please refer to this - Spring Security limit login attempts example,
create a table to store the values of failed attempts ex : user_attempts
Write custom event listener
#Component("authenticationEventListner")
public class AuthenticationEventListener
implements AuthenticationEventPublisher
{
#Autowired
UserAttemptsServices userAttemptsService;
#Autowired
UserService userService;
private static final int MAX_ATTEMPTS = 3;
static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
#Override
public void publishAuthenticationSuccess(Authentication authentication) {
logger.info("User has been logged in Successfully :" +authentication.getName());
userAttemptsService.resetFailAttempts(authentication.getName());
}
#Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
logger.info("User Login failed :" +authentication.getName());
String username = authentication.getName().toString();
UserAttempts userAttempt = userAttemptsService.getUserAttempts(username);
User userExists = userService.findBySSO(username);
int attempts = 0;
String error = "";
String lastAttempted = "";
if (userAttempt == null) {
if(userExists !=null ){
userAttemptsService.insertFailAttempts(username); }
} else {
attempts = userAttempt.getAttempts();
lastAttempted = userAttempt.getLastModified();
userAttemptsService.updateFailAttempts(username, attempts);
if (attempts + 1 >= MAX_ATTEMPTS) {
error = "User account is locked! <br>Username : "
+ username+ "<br>Last Attempted on : " + lastAttempted;
throw new LockedException(error);
}
}
throw new BadCredentialsException("Invalid User Name and Password");
}
}
3.Security Configuration
1) #Autowired
#Qualifier("authenticationEventListner")
AuthenticationEventListener authenticationEventListner;
2) #Bean
public AuthenticationEventPublisher authenticationListener() {
return new AuthenticationEventListener();
}
3) #Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//configuring custom user details service
auth.authenticationProvider(authenticationProvider);
// configuring login success and failure event listener
auth.authenticationEventPublisher(authenticationEventListner);
}