For my project I have used Spring 4.2.5, Spring Security 4.0.4 and JSF Mojarra. And I'll throw a custom exception from my custom UserDetailsService.
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException{
com.entity.User domainUser = userDAO.getUser(login);
boolean enabled = false;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
if(!enabled){
throw new DisabledException("User is disabled!");
}
return new User(domainUser.getLogin(),
domainUser.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(domainUser.getRole().getId()));
}
My LoginBean is:
public String login() {
RequestContext context = RequestContext.getCurrentInstance();
FacesMessage msg = null;
boolean loggedIn = false;
try {
SecurityContext secContext = SecurityContextHolder.getContext();
Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
secContext.setAuthentication(auth);
loggedIn = true;
msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Welcome", username);
return "/pages/index?faces-redirect=true";
} catch (DisabledException e) {
loggedIn = false;
msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login Error", e.getMessage());
} catch (ServletException e) {
loggedIn = false;
msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login Error", e.getMessage());
}
FacesContext.getCurrentInstance().addMessage(null, msg);
context.addCallbackParam("loggedIn", loggedIn);
return "/login";
}
So if I set "enabled" to true and provide right user credentials all works fine. But if I change it to false I can't see any exception in log and can't login.
I've try with this code. And it works fine but I can see only "Bad credentials" exception in any cases.
HttpServletRequest httpReq = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
httpReq.login(username, password);
I just started with java and spring, so can you explain me difference between http Req.login(username, password) and getContext.setAuthentication(auth). In first case I can retrieve authenticated user and in second case I get anonymously authenticated user.
Which is right solution for user authentication and custom UserDetailsService.
P.S. I'm using java based config.
Sorry If this question was previously answered. I can't find working solution.
UPDATE 1:
If I comment this
if(!enabled){
throw new DisabledException("User is disabled!");
}
In debug I can see default exception:
org.springframework.security.authentication.DisabledException: User is disabled
but I still can't catch this in my LoginBean. Why?
UPDATE 2:
I made some changes in my spring security config and now I can see locked, expired exceptions in my growl (primefaces) when I'm using HttpServletRequest httpReq.login(username, password).
So how I can customize exception messages?
And I still can't understand difference between HttpServletRequest login and UsernamePasswordAuthenticationToken authentication.
Related
I want to return custom message if user during authentication process is locked or expired. I tried to implement this:
#Service
public class UserDetailsHandler implements UserDetailsService {
#Autowired
private UsersService usersService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final Optional<Users> user = usersService.findByLogin(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User '" + username + "' not found");
}
return user
.map(value -> {
return new User(
value.getLogin(),
value.getEncryptedPassword(),
value.getEnabled(),
hasAccountExpired(value.getExpiredAt()),
hasPasswordExpired(value.getPasswordChangedAt()),
hasAccountLocked(value.getLockedAt()),
Collections.singleton(new SimpleGrantedAuthority(value.getRole().getAuthority()))
);
}).orElseThrow(() -> new UsernameNotFoundException("User with username " + username + " not found"));
}
private boolean hasAccountExpired(LocalDateTime account_expired_at) {
return account_expired_at == null;
}
Full code: GitHub
The question is how to create handlers which return some custom message if the validation returns true value for statuses user locked or user expired?
The best option for you is:
Implement Spring UserDetails in your entity Users.
Check in loadUserByUsername if the user has been locked, etc using Spring AccountStatusUserDetailsChecker class.
Add into your EngineExceptionHandler the required methods to manage those exceptions: LockedException, AccountExpiredException, etc
You will see examples of above points in the following links:
Point 1
Point 2
Points 2-3
Well I briefly look at your codes and you implement a JwtTokenFilter that will some how calls the UserDetailsHandler .
In JwtTokenFilter , you already catch and handle EngineException which contain the HTTP status codes and a message. An HTTP response will be sent out which the status and the body message that are the same as what defined in the caught EngineException
It seems that you already configure everything for it to work , so just simply throw EngineException with the suitable HTTP status code and message from the UserDetailsHandler . Something like :
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = usersService.findByLogin(username)
.map(value -> {
return new User(
value.getLogin(),
value.getEncryptedPassword(),
value.getEnabled(),
hasAccountExpired(value.getExpiredAt()),
hasPasswordExpired(value.getPasswordChangedAt()),
hasAccountLocked(value.getLockedAt()),
Collections.singleton(new SimpleGrantedAuthority(value.getRole().getAuthority()))
).orElseThrow(()-> throw new UsernameNotFoundException("User '" + username + "' not found"));
if (user.isAccountLock()){
throw new EngineException(HttpStatus.UNAUTHORIZED , "Custom message for account lock ......")
}
if(user.isAccountExpired()){
throw new EngineException(HttpStatus.UNAUTHORIZED , "Custom message for account expired... ......")
}
}
Spring Security uses the messages.properties which consist of default messages, we can add our custom message with the same. Add messages.properties and add a message as shown below.
AccountStatusUserDetailsChecker.expired=User account has expired
AccountStatusUserDetailsChecker.locked=User account is locked
AbstractUserDetailsAuthenticationProvider.expired=User account has expired
AbstractUserDetailsAuthenticationProvider.locked=User account is locked
You may find the default messages here
Its simple 2 steps approach. User expired means token expired
Step 1
Modify JWTTokenProvider Class to add a custom header to Http Servlet Request using setAttribute() method.
JwtTokenProvider.java
public boolean validateToken(String token,HttpServletRequest httpServletRequest){
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
}catch (SignatureException ex){
System.out.println("Invalid JWT Signature");
}catch (MalformedJwtException ex){
System.out.println("Invalid JWT token");
}catch (ExpiredJwtException ex){
System.out.println("Expired JWT token");
httpServletRequest.setAttribute("expired",ex.getMessage());
}catch (UnsupportedJwtException ex){
System.out.println("Unsupported JWT exception");
}catch (IllegalArgumentException ex){
System.out.println("Jwt claims string is empty");
}
return false;
}
Step 2
Modify commence method in JwtAuthenticationEntryPoint.class to check expired header in http servlet request header that we added in step 1.
JwtAuthenticationEntryPoint.java
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
final String expired = (String) httpServletRequest.getAttribute("expired");
System.out.println(expired);
if (expired!=null){
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
}else{
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
}
}
It is a best practice to return detailed error messages in any REST API. We used this to customize spring rest jwt token expired response to return more detailed error response. We can use this method not only for token expired but also for other jwt token exceptions like SignatureException, Malformed JwtException, UnsupportedJwtException and IllegalArgumentException.
I've started to use JHipster weeks ago and everything went find since now. I want to have a LDAP authentification with at the same time the default authentification of JHipster.
I followed this https://jhipster.github.io/tips/016_tip_ldap_authentication.html and it doesn't work as planned.
Actually my configuration is connecting well to my LDAP server and i know by viewing logs that the login search into the LDAP server and compare the password.
The problem is the login fail with the error :
UT005023: Exception handling request to /api/authentication
org.springframework.security.core.userdetails.UsernameNotFoundException: User nseys was not found in the database
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.lambda$onLoginSuccess$1(PersistentTokenRememberMeServices.java:116)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.onLoginSuccess(PersistentTokenRememberMeServices.java:116)
at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.loginSuccess(AbstractRememberMeServices.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
The thing is I want JHipster to automatically create the user in database when it doesn't exist in there with a mapping of parameters (but only when it's a LDAP user) and just connect if it's already done.
I've searched Spring-security solution aswell but the implementations are too far away from the initial files created by JHipster and I don't want to destroy all this.
Well I tried something that work, I don't know if this is how I should have done, but since I've found nothing about that, and it's not documented alot, I'll stick with that solution unless I find a better solution.
// PersistentTokenRememberMeServices.java
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
successfulAuthentication) {
String login = successfulAuthentication.getName();
log.debug("Creating new persistent login for user {}", login);
PersistentToken t = new PersistentToken();
t.setSeries(RandomUtil.generateSeriesData());
t.setTokenValue(RandomUtil.generateTokenData());
t.setTokenDate(LocalDate.now());
t.setIpAddress(request.getRemoteAddr());
t.setUserAgent(request.getHeader("User-Agent"));
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
t.setUser(u);
return t;
}).orElse(null);
if (token == null) {
if (successfulAuthentication.getPrincipal() instanceof LdapUserDetails) {
User ldapUser = new User();
ldapUser.setLogin(login);
ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
ldapUser.setActivated(true);
CustomLdapUserDetails customLdapUserDetails = (CustomLdapUserDetails) successfulAuthentication.getPrincipal();
ldapUser.setEmail(customLdapUserDetails.getEmail());
ldapUser.setFirstName(customLdapUserDetails.getFirstName());
ldapUser.setLastName(customLdapUserDetails.getLastName());
Set<Authority> authorities = new HashSet<>();
authorities.add(this.authorityRepository.findOneByName("ROLE_USER"));
ldapUser.setAuthorities(authorities);
ldapUser.setLangKey("fr");
userRepository.save(ldapUser);
t.setUser(ldapUser);
token = t;
} else {
throw new UsernameNotFoundException("User " + login + " was not found in the database");
}
}
...
}
And I added a contextMapper to get the attributes in the LDAP server
// SecurityConfiguration.java
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return new CustomLdapUserDetails((LdapUserDetails) details, ctx);
}
};
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getLdap().getUrl());
contextSource.setBase(applicationProperties.getLdap().getBase());
contextSource.setUserDn(applicationProperties.getLdap().getUserDn());
contextSource.setPassword(applicationProperties.getLdap().getPassword());
contextSource.afterPropertiesSet(); //needed otherwise you will have a NullPointerException in spring
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userSearchBase(applicationProperties.getLdap().getSearchBase()) //don't add the base
.userSearchFilter(applicationProperties.getLdap().getSearchFilter())
.contextSource(contextSource)
;
}
#Override
public void sessionDestroyed(HttpSessionEvent arg0)
{
boolean isRemoved = sessionIdSet.remove(arg0.getSession().getId());
if (isRemoved)
{
arg0.getSession().invalidate();
System.out.println(arg0.getSession().getAttribute("userName"));
System.out.println("session destroyed");
}
}
Suppose the attribute userName was testUser at the time of login. So after timeout in my java console I get null and session destroyed printed. So if it is null that means when I do following in my jsp I should get null but instead still I get testUser
$("body").click(function(event){
var property="<%=session.getAttribute("userName")%>";
//Here I expect property to be null as session is destroyed
//and it prints null in java so it should also here.
alert(property);
//But what i get here is testUser
}
Using Spring interceptor
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
boolean allowRequest = true;
String requestUri = request.getRequestURI().toString();
HttpSession session = request.getSession(false);
logger.info("Pre-intercepting request URI: " + requestUri);
try {
if(null != session) {
String sessionBelongsTo = (String) session.getAttribute("CUR_TYPE");
String user = (String) session.getAttribute("userName");
System.out.println(user);
if(!requestUri.endsWith("/login") && !requestUri.endsWith("/loginauth") && !requestUri.endsWith("sap-ui-core.js") && !requestUri.endsWith("/main")) {
if(null == user) {
logger.info(""
+ "Login required, redirecting to LOGIN page");
response.sendRedirect(request.getContextPath() + "/login");
allowRequest = false;
}
else {
logger.info("Login not required");
}
}
}
else{
logger.debug("session is null.redirecting to login");
session = request.getSession();
response.sendRedirect(request.getContextPath() + "/login");
allowRequest = false;
}
}catch(IOException ioe) {
logger.info(ioe.getMessage());
allowRequest = false;
}
return allowRequest;
}
Using interceptor makes an redirect call GET http://localhost:9090/app/login which is successfull but redirect never really happens.
You are mixing two different codes. You have to realize, where and when each code is executed - JSP on the server when the page is requested and rendered (i.e. before the response is send to the browser) and Javascript in the browser, after the browser receives the already generated response.
I.e. <%=session.getAttribute("userName")%> is processed on the server, and your browser receives e.g. var property="johndoe"; - the JSP code is NOT executed again when your onclick handler is executed.
Using Spring Boot I am configuring the following filter
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMapping = new HashMap<>();
/*
* URL path expressions are evaluated against an incoming request in the order they are defined and the FIRST MATCH WINS. For example, let's asume that there are the following chain definitions:
/account/** = ssl, authc
/account/signup = anon
If an incoming request is intended to reach /account/signup/index.html (accessible by all 'anon'ymous users), it will never be handled!. The reason is that the /account/** pattern matched the incoming request first and 'short-circuited' all remaining definitions.
Always remember to define your filter chains based on a FIRST MATCH WINS policy!
* */
filterChainDefinitionMapping.put("/login.html", "authc");
filterChainDefinitionMapping.put("/logout", "logout");
filterChainDefinitionMapping.put("/css/**", "anon");
filterChainDefinitionMapping.put("/register/**", "anon");
filterChainDefinitionMapping.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/login.html");
shiroFilter.setSuccessUrl("/");
shiroFilter.setUnauthorizedUrl("/unauthorized.html");
Map<String, Filter> filters = new HashMap<>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl("/login.html?logout");
filters.put("logout", logoutFilter);
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
return shiroFilter;
}
However, whenever I try to login with wrong credentials the redirection never happens. I do get the "shiroLoginFailure" attribute holding the UnknownUserException.
(Logging in with the correct credentials works fine)
Any ideas?
Mariosk89, how do you resolve the /login.html?
It might be need to resolve redirect like this:
#RequestMapping("/login")
public String login(String username, String password) {
Subject currentUser = SecurityUtils.getSubject();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
try {
currentUser.login(new UsernamePasswordToken(username, password));
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
return "login";
}
return "redirect:index";
} else {
return "login";
}
}
Reference: https://github.com/lenicliu/examples/tree/master/examples-spring-boot/examples-spring-boot-shiro
For more exception solution, refer http://shiro.apache.org/10-minute-tutorial.html
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
I'm using the following method in a Spring Controller to allow authentication via Ajax. It works, but it doesn't seem to create a cookie or anything that makes the authentication persistent.
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public LoginStatus login(#RequestParam("j_username") String username,
#RequestParam("j_password") String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
try {
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
return new LoginStatus(auth.isAuthenticated(), auth.getName());
} catch (BadCredentialsException e) {
return new LoginStatus(false, null);
}
}
What do I need to do to make the authentication persistent?
Make sure
you have SecurityContextPersistenceFilter configured
you are not setting create-session attribute of <security:http> tag to none.