Spring-Security : Optimizing get currently authenticated user… - java

I am working on a Spring-MVC application in which I am using Spring-Security for authentication. Due to excessive usage of getting the currently authenticated user mechanism, Profiler shows it as an 'Allocation Hotspot' and nearly 9.5kb memory is consumed for a single user. Is there any way to optimize this infrastructure.
PersonServiceImpl :
#Override
public Person getCurrentlyAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
} else {
return personDAO.findPersonByUsername(authentication.getName());
}
}
If I somehow always can push the user in some cache from where it is retrieved after first retrieving, atleast that should improve the performance, but I have no idea how to do that. Any suggestions are welcome. Thanks.

You can use some variants. But also you can try to ue only Spring:
1. Extends org.springframework.security.core.userdetails.User and add into your UserEntity (Person)
public class User extends org.springframework.security.core.userdetails.User {
private Person sysUser;
public User(Person sysUser) {
super(sysUser.getLogin(), sysUser.getPassword(), new ArrayList<>());
this.sysUser = sysUser;
}
public Person getYourUser(){
return sysUser;
}
}
Implements UserDetailsService using your User Entity and extended org.springframework.security.core.userdetails.User
#Service(value = "userService")
public class UserService implements UserDetailsService {
#Autowired
SecurityDAO securityDAO;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = null;
try {
Person su = securityDAO.getUserOnlyByLogin(login);
if (su != null)
user = new User(Person);
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
}
Configure your Spring (xml or Annotation):
<beans:bean id="userService" class="your.pack.UserService"/>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userService"/>
</authentication-manager>
use your method
#Override
public Person getCurrentlyAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
} else { User user = (User) authentication.getPrincipal();
return user.getYourUser();
}
}

Related

Spring security with user/password

So I followed some guides on spring security with usernames and passwords, however most of them show using "InMemoryUserDetailsManager" which they say should not be used in production:
#Bean
public InMemoryUserDetailsManager userDetailsManager(){
UserDetails admin = User.withDefaultPasswordEncoder()
.username("ADMIN")
.password("123")
.roles("ADMIN").build();
return new InMemoryUserDetailsManager(admin);
}
My questions, so how should a production level version of this be setup? Is it just not using the default password encoder because it is deprecated or should I use an entirely different method of adding and storing users?
You should implement jdbc authentication DaoAuthenticationProvider. Checkout https://www.baeldung.com/spring-security-jdbc-authentication.
Your user details must be stored in permanent storage, not temporary storage. Also, passwords must be encrypted to avoid compromising security. So using permanent storage you can take backup or data and run queries out of it.
You can implement custom user details service instead of using default.
#Service
public class CustomUserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public CustomUserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.NOT_FOUND, "user");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(),
true,
true,
true,
getAuthorities(user));
}
public Boolean isTokenValid(String token) {
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(token);
return true;
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.LOGIN_FAILURE, "invalid credentials");
} catch (ExpiredJwtException ex) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.LOGIN_FAILURE, "token expired");
}
}
#Transactional
public Boolean save(User user){
if(StringUtils.isEmpty(user.getUsername())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "username");
}
if(StringUtils.isEmpty(user.getPassword())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "password");
}
if(StringUtils.isEmpty(user.getEmail())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "email");
}
User registeredUser = new User();
registeredUser.setUsername(user.getUsername());
registeredUser.setPassword(passwordEncoder.encode(user.getPassword()));
registeredUser.setEmail(user.getEmail());
registeredUser.setEnabled(true);
registeredUser.setRoles(user.getRoles());
User savedUser = userRepository.save(registeredUser);
Inventory userInventory = inventoryService.saveInventoryForUser(savedUser.getUsername());
return userInventory != null;
}
private Set<GrantedAuthority> getAuthorities(User user){
Set<GrantedAuthority> authorities = new HashSet<>();
for(Role role : user.getRoles()) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName().getRole());
authorities.add(grantedAuthority);
}
return authorities;
}
}
You can save user details into your repository.
#Repository
public interface UserRepository extends BaseRepository<User> {
User findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
Finally add your user details into authentication manager with password encoder.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
}
#Override
public UserDetailsService userDetailsServiceBean() {
return new CustomUserDetailsServiceImpl(userRepository);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
For more details check my github repository
Hello Please Code as the following coding
#Bean
public InMemoryUserDetailsManager createUserDetailsManager() {
UserDetails userDetails1 = createNewUser("username1", "dummy");
UserDetails userDetails2 = createNewUser("username2", "dummydummy");
return new InMemoryUserDetailsManager(userDetails1, userDetails2);
}
private UserDetails createNewUser(String username, String password) {
Function<String, String> passwordEncoder
= input -> passwordEncoder().encode(input);
UserDetails userDetails = User.builder()
.passwordEncoder(passwordEncoder)
.username(username)
.password(password)
.roles("USER","ADMIN")
.build();
return userDetails;
}
Hope it help you

getting authenticated user inside the configuration class in Spring

I am using Spring Security to authenticate users. I need to resolve which user has authenticated in my ApplicationConfiguration to provide the correct data, but for some reason, the following code returns null:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
EDIT:
I basically want to inject a service into a controller. (PackagesBaseService)
This service is abstract, so in my bean definition (In configuration class) I need to resolve if PackagesBaseService is an instance of TablePackagesService or DeskPackagesService. This is based on which user is authenticated (this requirement cannot be changed).
I understand I could just test the Authenticated user even in my controller and instantiate my service there, but I would like to avoid that.
I am able though to retrieve the Auth user using this same from anywhere else.
Why I can't use this from a configuration file? Does it have something with the order that the beans are loaded?
How can I solve this?
Implementation:
Configuration:
#Bean
public PackagesBaseService packagesBaseService()
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
if (authentication.getName() == env.getProperty("tableFactory.username")) {
return new TablePackagesService();
}
if (authentication.getName() == env.getProperty("deskFactory.username")) {
return new DeskPackagesService();
}
// thrown Exception
}
// thrown Exception
}
Controller:
#Autowired
PackagesBaseService packagesService;
public MultiPackagesResponse data(#RequestParam("fromId") int fromId)
{
MultiPackagesResponse response = packagesService.getPackages(fromId);
return response;
}
You need to go with a factory to be able to use the user context. This COULD look like this:
Define a Factory bean:
#Service
public class PackageBaseServiceFactory {
public final HashMap < String,
PackageBaseService > packageBaseServiceCache = new HashMap();
public PackageBaseService getPackageService() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
PackageBaseService packageBaseService = packageBaseServiceCache.get(authentication.getName());
if (packageBaseService == null) {
packageBaseService = initPackagesBaseService(authentication.getName());
}
return packageBaseService;
}
}
private PackageBaseService initPackagesBaseService(String authenticationName) {
PackageBaseService packageBaseService;
if (authenticationName == env.getProperty("tableFactory.username")) {
packageBaseService = new TablePackagesService();
} else if (authenticationName == env.getProperty("deskFactory.username")) {
packageBaseService = new DeskPackagesService();
} else {
throw new IllegalStateException(); //or whatever you do
}
return packageBaseServiceCache.put(authenticationName, packageBaseService);
}
}
and used it this way
#Autowired
PackageBaseServiceFactory packageBaseServiceFactory;
public MultiPackagesResponse data( # RequestParam("fromId")int fromId) {
MultiPackagesResponse response = packageBaseServiceFactory.getPackageService().getPackages(fromId);
return response;
}

custom DaoAuthenticationProvider doesnt check password

I am implementing spring Security in a Java EE application (Spring / Struts / Hibernate). I have some truble with my custum DaoAuthenticationProvider.
#Override
public void configure(AuthenticationManagerBuilder pAuth) throws Exception {
pAuth.authenticationProvider(mAuthenticationProvider)
.userDetailsService(mUserDetailsService)
.passwordEncoder(new Md5PasswordEncoder());
}
This is in my SecurityConfig (extends WebSecurityConfigurerAdapter) class.
When I debug the app, I can see that in my custom DaoAuthenticationProvider the password encoder is not set (PlainTextPasswordEncoder instead of Md5), why ?
After that I tried to set manually in the constructor this values :
public LimitLoginAuthenticationProvider() {
setPasswordEncoder(new Md5PasswordEncoder());
setUserDetailsService(mUserDetailsService);
}
When I debug it I see the right values.
But in both cases, if I do :
#Override
public Authentication authenticate(Authentication pAuthentication) {
Authentication lAuth = super.authenticate(pAuthentication);
return lAuth;
}
The property of lAuth that indicate if user is authenticate or not is at true whatever the password is...
Does anyone have the answer ?
EDIT : LimitLoginAuthenticationProvider implementation
#Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private IUserDao mUserDao;
#Autowired
#Qualifier("userDetailsService")
UserDetailsService mUserDetailsService;
public LimitLoginAuthenticationProvider() {
setPasswordEncoder(new Md5PasswordEncoder());
}
#Autowired
#Qualifier("userDetailsService")
#Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
#Override
public Authentication authenticate(Authentication pAuthentication) {
Authentication lAuth = super.authenticate(pAuthentication);
return lAuth;
}
#Override
#Transactional(readOnly = true)
protected void additionalAuthenticationChecks(UserDetails pUserDetails,
UsernamePasswordAuthenticationToken pAuthentication)
throws AuthenticationException {
try {
User lUser = mUserDao.findUserByLogin(pAuthentication.getName());
if (lUser.getStatus() >= 3) {
logger.debug("User account is locked");
throw new LockedException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
} catch (DaoException e) {
}
}
}
Ok I think I missunderstood the objective of DaoAuthenticationProvider.
I think I have to check the password myself :
PasswordEncoder lPasswordEncoder = getPasswordEncoder();
if (!lPasswordEncoder.isPasswordValid(lUser.getPassword(),
pAuthentication.getCredentials().toString(), null)) {
throw new BadCredentialsException("Wrong password for user "
+ lUser.getLogin());
}
(am I wrong ?)

How to allow multiple sign in in Spring security?

I would like to enable multi sign-in for my spring security application i.e. For example, if I have two email addresses, I would want to allow the user to sign in with multiple email addresses and the user can shift from one email account to the other, just as Gmail Multiple Sign-in. How could I do that with Spring security?
There seems to be only one Principal instead of list of principals in the Spring security. Could I achieve it?
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Thanks in advance. Hope you will reply as soon as possible.
To some extent Spring Security supports user switch. It is more like a su under Linux.
Nevertheless, you can reuse some code from SwitchUserFilter to create your own user switch.
Primarily, you need to create
Custom Spring Security UserDetails which holds a list of usernames a switch is allowed to
Custom UserDetailsService which populates your custom UserDetails
Custom UserSwitchFilter based on Spring's SwitchUserFilter
Custom UserDetails and UserDetailsService are just examples here and may differ from your own implementation. The idea is to hold a list of usernames in UserDetails for later processing in custom UserSwitchFilter.
CustomUserDetails:
public class CustomUserDetails extends User {
private final Set<String> linkedAccounts;
public CustomUserDetails(String username, String password, Set<String> linkedAccounts, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.linkedAccounts = linkedAccounts;
}
public Set<String> getLinkedAccounts() {
return linkedAccounts;
}
}
CustomUserDetailsService:
public class CustomUserDetailsService implements UserDetailsService {
private UserDao userDao = ...;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BackendUser user = userDao.findUserByUsername(username);
return new CustomUserDetails(user.getHane(), ......);
}
}
Main differences to Spring Security UserSwitchFilter:
Added method checkSwitchAllowed checks if a switch to that specific user from current authenticated user is allowed
switch is based on query paramater and not url for better user experience (see requiresSwitchUser). hence no need for switchUserUrl and targetUrl
Custom UserSwitchFilter ha no notion of a exitUserUrl. Hence no need for exitUserUrl
createSwitchUserToken doesn't modify user authorities
CustomSwitchUserFilter:
public class CustomSwitchUserFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public static final String SPRING_SECURITY_SWITCH_USERNAME_KEY = "j_switch_username";
private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private String switchFailureUrl;
private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationFailureHandler failureHandler;
#Override
public void afterPropertiesSet() {
Assert.notNull(userDetailsService, "userDetailsService must be specified");
if (failureHandler == null) {
failureHandler = switchFailureUrl == null ? new SimpleUrlAuthenticationFailureHandler() :
new SimpleUrlAuthenticationFailureHandler(switchFailureUrl);
} else {
Assert.isNull(switchFailureUrl, "You cannot set both a switchFailureUrl and a failureHandler");
}
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// check for switch or exit request
if (requiresSwitchUser(request)) {
// if set, attempt switch and store original
try {
Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
} catch (AuthenticationException e) {
logger.debug("Switch User failed", e);
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
chain.doFilter(request, response);
}
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
if (username == null) {
username = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Attempt to switch to user [" + username + "]");
}
UserDetails targetUser = userDetailsService.loadUserByUsername(username);
userDetailsChecker.check(targetUser);
checkSwitchAllowed(targetUser);
// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);
if (logger.isDebugEnabled()) {
logger.debug("Switch User Token [" + targetUserRequest + "]");
}
// publish event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(SecurityContextHolder.getContext().getAuthentication(), targetUser));
}
return targetUserRequest;
}
private void checkSwitchAllowed(UserDetails targetUser) {
CustomUserDetails details = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String targetUsername = targetUser.getUsername();
//target username has to be in linked accounts otherwise this is an unauthorized switch
if(!details.getLinkedAccounts().contains(targetUsername)) {
throw new InsufficientAuthenticationException("user switch not allowed");
}
}
private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request, UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
// get the original authorities
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();
// add the new switch user authority
List<GrantedAuthority> newAuths = new ArrayList<GrantedAuthority>(orig);
// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), newAuths);
// set details
targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return targetUserRequest;
}
protected boolean requiresSwitchUser(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
return parameterMap.containsKey(usernameParameter);
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
throws BeansException {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setSwitchFailureUrl(String switchFailureUrl) {
Assert.isTrue(StringUtils.hasText(usernameParameter) && UrlUtils.isValidRedirectUrl(switchFailureUrl),
"switchFailureUrl cannot be empty and must be a valid redirect URL");
this.switchFailureUrl = switchFailureUrl;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
this.userDetailsChecker = userDetailsChecker;
}
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
}
Add CustomSwitchUserFilter to your security filter chain. It has to be placed after FILTER_SECURITY_INTERCEPTOR.
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userDetailsService"/>
</security:authentication-manager>
<security:http use-expressions="true">
<security:intercept-url pattern="/**" access="isFullyAuthenticated()" />
<security:form-login login-page="/login.do" />
<security:logout logout-success-url="/login.do" />
<security:custom-filter ref="switchUserProcessingFilter" after="FILTER_SECURITY_INTERCEPTOR" />
</security:http>
<bean id="switchUserProcessingFilter" class="security.CustomSwitchUserFilter">
<property name="userDetailsService" ref="userDetailsService" />
</bean>
You can find a working example here.

Spring Security anonymous user has acces to every url

I'm developing gwt application which I want to secure using spring-security. I have users data in database and UserService is responsible for getting particular User. I have followed this tutorial
AuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired UserService userService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
User user = userService.findByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
String storedPass = user.getPassword();
if (!storedPass.equals(password)) {
throw new BadCredentialsException("Invalid password");
}
Authentication customAuthentication = new CustomUserAuthentication(user, authentication);
customAuthentication.setAuthenticated(true);
return customAuthentication;
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
CustomAuthentication
public class CustomUserAuthentication implements Authentication {
private static final long serialVersionUID = -3091441742758356129L;
private boolean authenticated;
private final GrantedAuthority grantedAuthority;
private final Authentication authentication;
private final User user;
public CustomUserAuthentication(User user, Authentication authentication) {
this.grantedAuthority = new SimpleGrantedAuthority(user.getRole().name());
this.authentication = authentication;
this.user = user;
}
#Override
public Collection<GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(grantedAuthority);
return authorities;
}
#Override
public Object getCredentials() {
return authentication.getCredentials();
}
#Override
public Object getDetails() {
return authentication.getDetails();
}
#Override
public Object getPrincipal() {
return user;
}
#Override
public boolean isAuthenticated() {
return authenticated;
}
#Override
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
this.authenticated = authenticated;
}
#Override
public String getName() {
return user.getUsername();
}
}
security context:
<s:http auto-config="true" create-session="always" >
<s:intercept-url pattern="/index.html" access="ROLE_USER" />
<s:logout logout-success-url="/login.html"/>
<s:form-login login-page="/login.html" default-target-url="/index.html" authentication-failure-url="/login.html" />
</s:http>
<s:authentication-manager alias="authenticationManager">
<s:authentication-provider ref="customAuthenticationProvider" />
</s:authentication-manager>
<bean id="customAuthenticationProvider" class="com.example.server.security.CustomAuthenticationProvider" />
Everything works fine, spring intercept call to index.html i need to log and it redirects me back to index.html. The problem is when i log out and then go to index.html once again I just simply get access to it. I figured out that:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Logged as: " + auth.getName());
prints anonymousUser after logout. This code prints my user name when I log in again so I suppose that there is something wrong with intercepting anonymous user. Does anyone knows how to intercept anonymous user?
Instead of:
<s:intercept-url pattern="/**" access="ROLE_USER" />
You can use:
<s:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY,ROLE_USER" />
That should make Spring Security deny access to the anonymous user. Of course, that implies you also need to add one of these:
<s:intercept-url pattern="/url_that_should_be_accessible_to_anonymous_user" access="IS_AUTHENTICATED_ANONYMOUSLY" />
For every pattern that anonymous users should be able to access. Typically, login pages, error pages, static resources (images, PDF, etc).

Categories