I have created a custom AuthenticationProvider to perform custom security checks. I have also created custom exceptions that inherit from AccountStatusException to notify user status problems such as when the user has not verified his account for an specific period of time.My UserDetails is also acustom implementation.
Here is the code for the security checks I perform. Code that is irrelevant to the case has been omitted.
public class SsoAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
User user = null;
if (username != null) {
user = getUserRepository().findByUserName(username);
if (user != null) {
if (user.getEnabled() != 0) {
if ((user.getUserDetail().getConfirmed() != 0)
|| ((new Date().getTime() - user.getUserDetail().getRequestDate().getTime()) / (1000 * 60 * 60 * 24)) <= getUnconfirmedDays()) {
if (getPasswordEncoder().isPasswordValid(user.getPassword(),
(String) authentication.getCredentials(), user)) {
user.authenticated = true;
user.getAuthorities();
}
} else {
throw new UserNotConfirmedAndTimeExceeded(
"User has not been cofirmed in the established time period");
}
} else {
throw new DisabledException("User is disabled");
}
} else {
throw new BadCredentialsException("User or password incorrect");
}
} else {
throw new AuthenticationCredentialsNotFoundException("No credentials found in context");
}
return user;
}
}
The SsoAuthenticationProvider checks:
That the username is registered (exists in the db)
That the user has confirmed his email
If the user has not confirmed his email, check that he is still in the grace period (this is a few days we give users to confirm their email while letting them access the site)
If the user has not confirmed email and he is not in the grace period, throw security exception to signal these status and reject authentication
The problem is that not all of these exceptions are thrown up the stack up to the controller so it seems impossible to inform the user about the login problem.
Using UserDetails methods such as isEnabled() (and similar) is not a possibility as the semantics of our different user account statuses are completely different.
Is this the right approach to build custom security with custom exceptions? Should i implement sth else to make this work?
To close the previously asked question let me explain what we did.
As I commented to previous responses, using provided methods in UserDetails objectis not feasible as you cannot capture all the login failure semantics with the given methods. In our case these semantics are still very limited but in other cases it could indfinitely extend over time to express different user situations.
The exception approach was finally the best one. The final code looks like this
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username=(String)authentication.getPrincipal();
User user=null;
if(username!=null){
user=getUserRepository().findByUserName(username);
if(user!=null){
if(user.getEnabled()!=0){
if((user.getUserDetail().getConfirmed()!=0)||((new Date().getTime()-user.getUserDetail().getRequestDate().getTime())/(1000 * 60 * 60 * 24))<=getUnconfirmedDays()){
if(getPasswordEncoder().isPasswordValid(user.getPassword(), (String)authentication.getCredentials(), user)){
user.authenticated=true;
user.getAuthorities();
} else {
throw new BadCredentialsException("Password incorrect");
}
}else{
throw new UserNotConfirmedAndTimeExceeded("User has not been cofirmed in the established time period");
}
}else{
throw new DisabledException("User is disabled");
}
}else{
throw new BadCredentialsException("User does not exist");
}
}else{
throw new AuthenticationCredentialsNotFoundException("No credentials found in context");
}
return user;
}
All exceptions are part of the spring security exception stack. This is, those custom exceptions inherit from some existing exception. Then, in your security controller you should check for security exceptions and treat them as desired. For example redirecting to different pages.
Hope this helps!
i think its better to use other method/properties of user detail object for this purpose.
like
isAccountNonExpired()
isAccountNonLocked()
isEnabled()
and if you want to display custom error message then use message properties as explained in this article
Related
im using javax.servlet.http.HttpServletRequest; to get the request in java controller, i just want to create controller that returns actual user name, im using method getUserPrincipal() in order to get the user in the actual session, it doesn't work at the first page load (returns null) but when i reload the page it works perfectly.
Im using
spring framework 5.3.9
javaee-api 8.0
weblogic 14.1.1
vuejs 3.2.37
Here is my java controller
#GetMapping("/username")
public ResponseEntity<?> getname(HttpServletRequest request) {
Map<String, String> response = new HashMap<String, String>();
String username = "";
try {
Principal p = request.getUserPrincipal();
if (p == null || p.getName() == null || p.getName().equals("")) {
username = "undefined";
} else {
username = p.getName();
}
response.put("username", username);
return new ResponseEntity<Object>(
response, HttpStatus.OK);
} catch (Exception ex) {
System.out.println(ex.getMessage());
response.put("username", "");
return new ResponseEntity<Object>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I have tried something a little tricky, reload the page in my Vue file when username is undefined, it works but i need this to work at first load.
Can you help me with this issue?
Thanks
Is it possible the first request is sent without user authentication?
That's what browsers would typically do, and only when the server responds with HTTP status 403 they would try again with credentials.
To not deal with such fuss in your application you could simply set a security constraint that only allows authenticated users to access. The container will then automatically send the 403 response and your application would only see valid traffic.
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 have configured the server.xml file in a Java Spring application to authenticate users when logging in from database tables and roles. I'm wondering how in Java code can I check who's logged in to the application?
I know that in a jsp file I can just use this following syntax to show the name:
${pageContext.request.userPrincipal.name} .
In your Spring MVC Controller, just add the following statement:
String loggedUser = request.getUserPrincipal().getName();
where request is the object of HttpRequest type, made available to you by Spring on demand.
There is very beautiful article for this is given at http://www.baeldung.com/spring-security-track-logged-in-users
You can leverage the HttpSessionBindingListener to update the list of logged in users whenever user information is added to the session or removed from the session based on user logs into the system or logs out from the system.
It will listen to events of type HttpSessionBindingEvent, which are triggered whenever a value is set or removed, or, in other words, bound or unbound, to the HTTP session.
#Component
public class LoggedUser implements HttpSessionBindingListener {
private String username;
private ActiveUserStore activeUserStore;
public LoggedUser(String username, ActiveUserStore activeUserStore) {
this.username = username;
this.activeUserStore = activeUserStore;
}
public LoggedUser() {}
#Override
public void valueBound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (!users.contains(user.getUsername())) {
users.add(user.getUsername());
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (users.contains(user.getUsername())) {
users.remove(user.getUsername());
}
}
// standard getter and setter
}
You can go through the whole code here
You can also retrieve the current logged in user from Spring security
Go through this artical
Or through Request also
request.getUserPrincipal().getName();
You can write a method to get current logged in user as you might need this various places like below :
public User getCurrentLoggedInUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof User) {
return ((User) principal);
}
}
}
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)
;
}
Let's say I have a Spring MVC, with Spring Security. I have a controller method where I want to get at my grandmother's recipes.
RequestMapping(value="/Recipe/{recipeId}", method = RequestMethod.GET)
Public Recipe getRecipe(#PathVariable("recipeId") int recepieId) {
Recipe recipe = database.getRecipeById(recepieId);
if(recipe.isBisket()) {
return recipe;
}
if(recipe.isSecretCookieRecipe()) {
boolean isAuthenitacted = Utils.authenticatUser(user);
if(isAuthenticated() {
return recipe;
} else {
// do something to authenitcate and then return the recipie
}
}
}
now the problem lies in the Spring security context. If I do something like this:
<security:intercept-url pattern="/Recipe/*" access="IS_AUTHENTICATED_FULLY" />
and
<beans:bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="useReferer" value="true" />
</beans:bean>
Now obviously in this scenario, isAuthenticated() will always return true as the intercept pattern will handle it. So let me remove the intercept url, and try this REST URL CALL :
GET http://bestrecepiesever/Recipe/1
Now 1 is a biskett recipe, so anyone can get at it and the call works. Now I want a cookie so I try this:
GET http://bestrecepiesever/Recipe/420
Now my Utils class will check my session, see that I am not logged it and throw an exception. And here is where my question lies... I want to be able to bookmark http://bestrecepiesever/Recipe/420. When I go there I get re-directetd to my login page, I login, I get re-directed back to the recipe.
How do I do this with Spring Security? What do I put where // do something to authenticate that will see that I am not authenticated at this time, send me to the login page, and then back to the recipe.
To complicate things, we have a SimpleMappingExceptionResolver that will intercept any and all exceptions and redirect you to a nice your page cannot be found. And yes, I tried createing my own that redirected to the login page, which worked, but would not the go back to the referer page. You can see that SO question here : Overriding HandlerExceptionResolver not useing Referer
UPDATE
as per #chaoluo here is the getAuthentication method in the utils:
public static User getUser(IDaoFactory daoFactory) throws Exception {
String authClass = SecurityContextHolder.getContext().getAuthentication().getPrincipal().getClass().getName();
authClass = SecurityContextHolder.getContext().getAuthentication().getAuthorities().getClass().getName();
if (authClass == null) {
authClass = "[ unknown ]";
}
if (SecurityContextHolder.getContext().getAuthentication().getName().toLowerCase().equals("anonymoususer")) {
throw new Exception("not authorized");
}
User theUser = new MyUserDetailsService(daoFactory).loadUserByUsername(SecurityContextHolder.getContext().getAuthentication().getName());
if (theUser == null) {
logger.error("Unable to find user in DB for " + SecurityContextHolder.getContext().getAuthentication().getName());
}
return theUser;
}
However, this is completly different from MyAuthenticationProvider which is used in the SpringSecurity like this:
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="myAuthenticationProvider"/>
</security:authentication-provider>
</security:authentication-manager>
And the authenticate Method:
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException
{
String username = authentication.getName();
String password = (String) authentication.getCredentials();
//System.out.println(org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable));
IDaoFactory daoFactory = ServiceFactory.getDaoFactory();
try {
QadoUserDetailsService userService = new QadoUserDetailsService(daoFactory);
User user = userService.loadUserByUsername(username);
if (user == null) {
throw new BadCredentialsException(BAD_USER);
}
if (user.getPassword() == null || user.getSalt() == null ||
!PasswordEncryption.hashEquals(user.getSalt(), user.getPassword(), password)) {
throw new BadCredentialsException(BAD_PASSWORD);
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(username, password, authorities);
}
finally {
daoFactory.cleanup();
}
}
So it there a way to authenticate via the MyAuthenticationProvider inside the controller?
You are making things to complex. Instead of #PreAuthorize use #PostAuthorize to do the validation. This will keep your controller clean and let Spring Security handle those things.
#RequestMapping(value="/Recipe/{recipeId}", method = RequestMethod.GET)
#PostAuthorize("returnObject.bisket || (returnObject.secretCookieRecipe && isAuthenticated())")
public Recipe getRecipe(#PathVariable("recipeId") int recepieId) {
return database.getRecipeById(recepieId);
}
Now Spring Security will validate the returnObject with the current user.