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;
}
Related
I have been trying to develop an application which shall load the authorization URL's from database. This application shall not have any user interface.
When I try to send request's through postman it gives me an error of "403". If I add
.antMatchers("/login").permitAll();
in the top then the application permits all request which is authenticated successfully whether the user have that authorization or not. Below my codes are given
WebSecurityConfig.cass
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll();
//Module List are holding modules which shall provide the URL's and authority names
List<Module> moduleList = _moduleDAO.getModule();
for (Module module : moduleList) {
if(module.getUrl() != null & module.getModuleName() != null) {
String url = module.getUrl();
String authority = module.getModuleName();
String requestType = module.getRequestType();
if(requestType != null) {
if(requestType == "GET") {
http.authorizeRequests().antMatchers(HttpMethod.GET,url).hasAuthority(authority);
}else if(requestType == "POST") {
http.authorizeRequests().antMatchers(HttpMethod.POST, url).hasAuthority(authority);
}else if(requestType == "DELETE") {
http.authorizeRequests().antMatchers(HttpMethod.DELETE,url).hasAuthority(authority);
}else if(requestType == "PUT") {
http.authorizeRequests().antMatchers(HttpMethod.PUT, url).hasAuthority(authority);
}
}
logger.info("-- Adding URL : " +url +" Request Type : "+requestType+" With Authority : " + authority);
}
}
http
.authorizeRequests()
.anyRequest().denyAll().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login");
}
UserPrinciple.class
public class UserPrinciple implements UserDetails{
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
//Get Roles
if(this.roleList != null) {
this.roleList.stream().distinct().forEach(role -> {
if(role != null && role.getRole() != null) {
String RoleName = "ROLE_"+role.getRole();
logger.info("#Adding "+RoleName + " to the authority");
GrantedAuthority authority = new SimpleGrantedAuthority(RoleName);
authorities.add(authority);
}
});
//Get Modules
if(this.moduleList != null) {
this.moduleList.forEach(module ->{
String moduleName = module.getModuleName();
logger.info("#Adding "+moduleName + " to the authority");
GrantedAuthority authority = new SimpleGrantedAuthority(moduleName);
authorities.add(authority);
});
}
}
return authorities;
}
In database I have stored these modules as described in the below link:
https://pasteboard.co/JdHgh5W.png
I don't know why but it appears that the only fault I had done is in the if condition. While debugging I have found that my statements under these if condition was not being executed. So I have changed my code statement approach.
I have just changed this
if(requestType == "GET") {
into
if(requestType.equals("GET")) {
It turns out that there was some problem in database too. The request types were not matching in "if condition" maybe in database the string was stored in different format. I have copied the request type string (i.e. GET, POST) from the log console of the IDE and pasted into the database columns.
And Everything started to work perfectly now. :V
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();
}
}
I am using #With(Action.class) annotation to intercept the calls to specific controller/actions. I am trying to retrieve the session from database on in the interceptor function; however the JPA helper class is not available in the Action.class interceptor method "call".
Can someone please guide on how to retrieve database entities in the interceptor functions?
Thanks.
Interceptor class:
public class SecuredAction extends Simple {
public SecuredAction() {
// TODO Auto-generated constructor stub
}
#Override
public Promise<Result> call(Context ctx) throws Throwable {
// check isContactVerified/isEmailVerified
String sid = getSidFromCookie(ctx);
if (sid != null) {
Session appSession = (Session) JPA.em().createNamedQuery("Session.findBySessionId").getSingleResult();
User user = appSession.getUserId();
if (user != null) {
ctx.args.put("user", user);
return delegate.call(ctx);
}
}
Result unauthorized = Results.unauthorized("Invalid Session");
return F.Promise.pure(unauthorized);
}
private String getSidFromCookie(Http.Context ctx) {
return ctx.session().get(AppConstants.COOKIE_USER_SESSIONID);
}
}
Error:
[RuntimeException: No EntityManager bound to this thread. Try to annotate your action method with #play.db.jpa.Transactional]
Wrap body of you action with JPA.withTransaction:
return JPA.withTransaction(
"default",
false, () -> {
String sid = getSidFromCookie(ctx);
if (sid != null) {
Session appSession = (Session) JPA.em().createNamedQuery("Session.findBySessionId").getSingleResult();
User user = appSession.getUserId();
if (user != null) {
ctx.args.put("user", user);
return delegate.call(ctx);
}
}
Result unauthorized = Results.unauthorized("Invalid Session");
return F.Promise.pure(unauthorized);
}
);
And do not annotate method with #Transactional if you annotated it with #With(SecuredAction.class).
I have spring(4.0.6) web application. I've noticed that when user logon then logout and try to logon once again gets an error about user\pass.
I don't have web.xml file because I am using SpringBootServletInitializer class to config my application.
I've add to my configuration such bean
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher()
{
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
and in security config I have this:
http.sessionManagement()
.maximumSessions(1).expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
As I understand previous session is alive and because of Maximum session = 1 user can't login once again. What should I do to avoid it?
You should invalidate the user session when he logout. Below is the piece of code. Need to pass the userid.
UserInfo is the POJO
public boolean invalidateUserSession(Long userId) {
List<Object> principalsList = sessionRegistry.getAllPrincipals();
Object targetPrincipal = null;
for (Object principal : principalsList) {
if (principal instanceof UserInfo) {
if (((UserInfo) principal).getId().equals(userId)) {
targetPrincipal = principal;
break;
}
}
}
if (targetPrincipal == null) {
return false;
}
List<SessionInformation> userSessionsList = sessionRegistry.getAllSessions(targetPrincipal, false);
for (SessionInformation x : userSessionsList) {
x.expireNow();
}
return true;
}
So I have set up my shiro to have two Realms. A Username and Password Realm, using the standard UsernamePasswordToken. I have also set up a Custom Bearer Authentication Token that works off a token passed in from the user.
If i just use my passwordValidatorRealm it works find, if no user is found throws unknown account, if password doesn’t match throws incorrect credentials, perfect. But as soon as i put in my tokenValidatorRealm it throws a
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms.
In this instance my tokenValidatorRealm returns null as no token was provided, so it moves on to the passwordValidatorRealm and just breaks.
Any ideas why introducing a second Realm will cause my working passwordValidatorRealm to break?
Have tried with different authentication strategies, and no luck there.
Using shiro 1.2.2
EDIT
I have two implementations, one for password and one for token
Password:
public class PasswordAuthorizingRealm extends AuthenticatingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof UsernamePasswordToken) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
char[] password = usernamePasswordToken.getPassword();
if (username == null) {
throw new AccountException("Null usernames are not allowed by this realm!");
}
//Null password is invalid
if (password == null) {
throw new AccountException("Null passwords are not allowed by this realm!");
}
UserService userService = new UserServiceImpl();
User user = userService.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException("Could not authenticate with given credentials");
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, user.getPassword(), "passwordValidatorRealm");
return simpleAuthenticationInfo;
} else {
return null;
}
}
}
and Bearer Token
public class TokenAuthorizingRealm extends AuthorizingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof BearerAuthenticationToken) {
BearerAuthenticationToken bearerAuthenticationToken = (BearerAuthenticationToken) authenticationToken;
String username = "" + bearerAuthenticationToken.getPrincipal();
User user = userService.getUserByUsername(username);
//User with such username has not found
if (user == null) {
throw new UnknownAccountException("Could not authenticate with given credentials");
}
BearerAuthenticationInfo bearerAuthenticationInfo = new BearerAuthenticationInfo(user);
return bearerAuthenticationInfo;
}
}
Shiro config
[main]
hashService = org.apache.shiro.crypto.hash.DefaultHashService
hashService.hashIterations = 500000
hashService.hashAlgorithmName = SHA-256
hashService.generatePublicSalt = true
hashService.privateSalt = ****
passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordService.hashService = $hashService
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService = $passwordService
authc = my.BearerTokenAuthenticatingFilter
tokenValidatorRealm = my.TokenAuthorizingRealm
passwordValidatorRealm = my.PasswordAuthorizingRealm
passwordValidatorRealm.credentialsMatcher = $passwordMatcher
securityManager.realms = $tokenValidatorRealm,$passwordValidatorRealm
These have been stripped out a bit, removed logging and other unnecessary code
The BearerTokenAuthenticatingFilter, just basically checks if a token has been supplied in the header if has
private void loginUser(ServletRequest request, ServletResponse response) throws Exception {
BearerAuthenticationToken token = (BearerAuthenticationToken) createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken "
+ "must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.sendRedirect("login");
}
}
BearerAuthenticationInfo class
public class BearerAuthenticationInfo implements AuthenticationInfo {
private final PrincipalCollection principalCollection;
private final User user;
public BearerAuthenticationInfo(User user) {
this.user = user;
this.principalCollection = buildPrincipalCollection(user);
}
public PrincipalCollection getPrincipals() {
return principalCollection;
}
public Object getCredentials() {
return user.getUsername();
}
private PrincipalCollection buildPrincipalCollection(User user) {
Collection<String> principals = new ArrayList<String>();
principals.add(user.getUsername());
return new SimplePrincipalCollection(principals, "tokenValidatorRealm");
}
}
Looks like it is expected behavior.
If you look at the javadoc for ModularRealmAuthenticator:
* #throws AuthenticationException if the user could not be authenticated or the user is denied authentication
* for the given principal and credentials.
*/
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
If you are having problems with the exception, you might need to change the code that calls the authentication to expect this exception.
Left for other searches:
You might have a missing supports method in your TokenAuthorizingRealm class.
Something like
#Override
public boolean supports(AuthenticationToken token) {
return token instanceof BearerAuthenticationToken;
}
should be present.
This discussion help me solve a similar problem. I wanted to authenticate a user by the application itself, not using any Shiro default implementation. To do that we must subclass AuthenticatingRealm, override doGetAuthenticationInfo and declare this realm as the validation one.
public class PasswordAuthorizingRealm extends AuthenticatingRealm {
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
In Shiro.ini:
passwordValidatorRealm = my.PasswordAuthorizingRealm