I have a bean which I've declared in my bean config as thus:
#Configuration
public class BeanConfig {
#Bean
public MemberDTO getMemberDTO() {
return new MemberDTO();
}
}
When a user calls my service, I use the username and password they've provided to call the endpoint of a different service to get the user's information:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
private #Autowired MemberDTO memberDTO;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String loginGeniuneFailMessage = "";
boolean loginGeniuneFail = false;
try {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String endPoint = credentialsBaseUrl + "/api/login";
HttpResponse<MemberDTO> response_auth = Unirest.get(endPoint)
.basicAuth(username, password)
.header("Accept", "*/*")
.asObject(MemberDTO.class);
int status_auth = response_auth.getStatus();
if (status_auth == 200) {
if (response_auth.getBody() == null) {
LOGGER.info("account validation - could not parse response body to object");
UnirestParsingException ex = response_auth.getParsingError().get();
LOGGER.error("parsing error: ", ex);
} else {
memberDTO = response_auth.getBody();
}
}
...
} catch (Exception ex) {
...
}
}
I want to store the user's information in the memberDTO and use the memberDTO elsewhere in a different component, rather than calling the login API every time:
#Component
public class MemberLogic {
private #Autowired MemberDTO memberDTO;
public ResponseEntity<?> processMemberInformation(WrapperDTO wrapperDTO, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.badRequest().body("An error occured while trying to persist information: " + errors);
}
String name = memberDTO.getName();
...
}
}
The problem now is the "memberDTO.getName()" is returning null, even though this value is being set from the initial API call in CustomAuthenticationProvider.
My questions are: why isn't this working? And is this the best approach to take for something like this?
Thanks.
My questions are: why isn't this working? And is this the best approach to take for something like this?
This doesn't work because Java uses pass-by-value semantics instead of pass-by-reference semantics. What this means is that the statement memberDTO = response_auth.getBody(); does not really make the Spring container start pointing to the MemberDTO returned by response_auth.getBody(). It only makes the memberDTO reference in CustomAuthenticationProvider point to the object in the response. The Spring container still continues to refer to the original MemberDTO object.
One way to fix this would be to define a DAO class that can be used for interacting with DTO instances rather than directly creating a DTO bean :
#Configuration
public class BeanConfig {
#Bean
public MemberDAO getMemberDAO() {
return new MemberDAO();
}
}
CustomAuthenticationProvider can then set the MemberDTO in the MemberDAO by using : memberDAO.setMemberDTO(response_auth.getBody());
Finally, MemberLogic can access the MemberDTO as String name = memberDAO.getMemberDTO().getName();
Note : Instead of returning the MemberDTO from the MemberDAO, the MemberDAO can define a method called getName which extracts the name from the MemberDTO and returns it. (Tell Don't Ask principle). That said and as suggested in the comments, the best practice would be to use a SecurityContext to store the user information.
The problem is, that you can not override a spring bean "content" like this memberDTO = response_auth.getBody(); because it changes only the instance variable for the given bean. (And its also not good because its out of the spring boot context and it overrides only the field dependency for this singleton bean)
You should not use a normal spring bean for holding data (a state). All the spring beans are singleton by default and you could have some concurrency problems.
For this you should use a database, where you write your data or something like a session bean.
Related
I added Spring Security on a Rest API with JWT authentication. Now I need to retrieve some data from the token in every controller method - be it either the username or other information.
Since almost all of my controller methods would need a Principal variable, is there a way to avoid declaring it as an argument to each method?
I once used ObjectProvider to do a similar thing, like:
#RequestScope
#Component
public class MyObj // ...
Usage:
#Component
public class OtherObj {
#Autowired
private ObjectProvider<MyObj> provider;
// ...
#Override
public boolean meth() throws Exception {
MyObj o = provider.getIfAvailable();
// ...
But there I found that if no instance exists, it is created instead of being returned null or an exception being thrown.
You can create one utility class, which provides you the principal.
public static Principal getPrincipal() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getPrincipal();
}
Ofcourse, here you would need to put the null checks in case the context or authentication is null.
I would like to get the username of the user in every request to add them to log file.
This is my solution:
First, I created a LoggedUser with a static property:
public class LoggedUser {
private static final ThreadLocal<String> userHolder =
new ThreadLocal<>();
public static void logIn(String user) {
userHolder.set(user);
}
public static void logOut() {
userHolder.remove();
}
public static String get() {
return userHolder.get();
}
}
Then I created a support class to get username:
public interface AuthenticationFacade {
Authentication getAuthentication();
}
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
#Override
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Finally, I used them in my Controllers:
#RestController
public class ResourceController {
Logger logger = LoggerFactory.getLogger(ResourceController.class);
#Autowired
private GenericService userService;
#Autowired
private AuthenticationFacade authenticationFacade;
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
loggedUser.logIn(authenticationFacade.getAuthentication().getName());
logger.info(LoggedUser.get()); //Log username
return userService.findAllRandomCities();
}
}
The problem is I don't want to have AuthenticationFacade in every #Controller, If I have 10000 controllers, for example, it will be a lot of works.
Do you have any better solution for it?
The solution is called Fish Tagging. Every decent logging framework has this functionality. Some frameworks call it MDC(Mapped Diagnostic Context). You can read about it here and here.
The basic idea is to use ThreadLocal or InheritableThreadLocal to hold a few key-value pairs in a thread to track a request. Using logging configuration, you can configure how to print it in the log entries.
Basically, you can write a filter, where you would retrieve the username from the security context and put it into the MDC and just forget about it. In your controller you log only the business logic related stuff. The username will be printed in the log entries along with timestamp, log level etc. (as per your log configuration).
With Jhovanni's suggestion, I created an AOP annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogUsername {
}
In the same package, I added new #Aop #Component class with AuthenticationFacade injection:
#Aspect
#Component
public class LogUsernameAop {
Logger logger = LoggerFactory.getLogger(LogUsernameAop.class);
#Autowired
private AuthenticationFacade authenticationFacade;
#Before("#annotation(LogUsername)")
public void logUsername() throws Throwable {
logger.info(authenticationFacade.getAuthentication().getName());
LoggedUser.logIn(authenticationFacade.getAuthentication().getName());
}
}
Then, in every #GetMapping method, If I need to log the username, I can add an annotation before the method:
#PostMapping
#LogUsername
public Course createCourse(#RequestBody Course course){
return courseService.saveCourse(course);
}
Finally, this is the result:
2018-10-21 08:29:07.206 INFO 8708 --- [nio-8080-exec-2] com.khoa.aop.LogUsername : john.doe
Well, you are already accesing authentication object directly from SecurityContextHolder, you can do it in your controller.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null){
//log user name
logger.info(authentication.get());
}
return userService.findAllRandomCities();
}
If you do not want to put all this in every endpoint, an utility method can be created to extract authentication and return its name if found.
public class UserUtil {
public static String userName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication == null ? null : authentication.getName();
}
}
and call it in your endpoint like
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//log user name
logger.info(UserUtil.username());
return userService.findAllRandomCities();
}
However, you are still adding lines of code in every endpoint, and after a few of them it starts to feel wrong being forced to do it. Something I suggest you to do is try aspect oriented programming for this kind of stuff. It will require you to invest some time in learning how it works, create annotations or executions required. But you should have it in a day or two.
With aspect oriented your endpoint could end like this
#RequestMapping(value ="/cities")
#LogUserName
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//LogUserName annotation will inform this request should log user name if found
return userService.findAllRandomCities();
}
of course, you are able to remove #LogUserName custom annotation and configure the new aspect with being triggered by methods inside a package, or classes extending #Controller, etc.
Definitely it is worth the time, because you can use aspect for more than just logging user name.
You can obtain the username via request or parameter in your controller method. If you add Principal principal as a parameter, Spring Ioc Container will inject the information regarding the user or it will be null for anonymous users.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Principal principal){
if(principal == null){
// anonymous user
}
}
There are various ways in Spring Security to fetch the user details from the security context. But according to your requirement, you are only interested in username, so you can try this:
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Authentication authentication){
logger.info(authentication.getName()); //Log username
return userService.findAllRandomCities();
}
Hope this helps!
First of all, according to Spring doc
, if i want to map user roles to scopes, i should use setCheckUserScopes(true) to DefaultOAuth2RequestFactory. So one way to do this, is injecting my own DefaultOAuth2RequestFactory bean, as doc says:
The AuthorizationServerEndpointsConfigurer allows you to inject a custom OAuth2RequestFactory so you can use that feature to set up a factory if you use #EnableAuthorizationServer.
Then i do
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
...
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.tokenServices(tokenServices());
endpoints
.getOAuth2RequestFactory(); // this doesn't return me my own DefaultOAuth2RequestFactory
}
#Bean
#Primary
public OAuth2RequestFactory defaultOAuth2RequestFactory() {
DefaultOAuth2RequestFactory defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(
clientDetailsService);
defaultOAuth2RequestFactory.setCheckUserScopes(true);
return defaultOAuth2RequestFactory;
}
}
EDIT
I've overlooked the method requestFactory() from AuthorizationServerEndpointsConfigurer. That was the correct way to pass it to Spring Security. Setting OAuth2RequestFactory bean as primary didn't work. I've deleted some things to focus on the real problem:
After this observation, the actual problem:
as i understand, if the user has authorities A and B, and the app has scope A, then he gets just 'A' scope. But this is not happening. What is really happening is that if app has scope A, and APP (not user) has authorities A and B, then user gets A. But this doesn't make any sense.
This is DefaultOAuth2RequestFactory method that resolve user's scopes:
private Set<String> extractScopes(Map<String, String> requestParameters, String clientId) {
... // I avoid some unimportant lines to not make this post so long
if ((scopes == null || scopes.isEmpty())) {
scopes = clientDetails.getScope();
}
if (checkUserScopes) {
scopes = checkUserScopes(scopes, clientDetails);
}
return scopes;
}
private Set<String> checkUserScopes(Set<String> scopes, ClientDetails clientDetails) {
if (!securityContextAccessor.isUser()) {
return scopes;
}
Set<String> result = new LinkedHashSet<String>();
Set<String> authorities = AuthorityUtils.authorityListToSet(securityContextAccessor.getAuthorities());
for (String scope : scopes) {
if (authorities.contains(scope) || authorities.contains(scope.toUpperCase())
|| authorities.contains("ROLE_" + scope.toUpperCase())) {
result.add(scope);
}
}
return result;
}
Is this a bug? Please tell me if i am wrong. Regards
You need to wire your OAuth2RequestFactory by code something like here.
If the authorities are set by ClientDetailsService then you should be good. If you are looking to map logged-in user authorities I don't have luck there either.
I've implemented Persistent token based Remember Me with Spring Security 3.2.3.RELEASE.
During development and testing I realized the database is filled with tokens for the same username.
When removeUserTokens is called I don't know which of the tokens I need to delete. I guess that the user have multiple tokens, one for each device he's using (Computer, Android, etc...), and if he logs out of one device, I want to delete the token for that device so he stays logged in another device.
Any ideas?
1) create custom PersistentTokenRepository with one additional method:
public class MDJdbcTokenRepository extends JdbcTokenRepositoryImpl {
public void removeTokenBySeries(String series) {
getJdbcTemplate().update("delete from persistent_logins where series = ?", series);
}
}
2) create custom RememeberMeServices
public class MDRememberMeServices extends PersistentTokenBasedRememberMeServices {
private MDJdbcTokenRepository tokenRepository;
public MDRememberMeServices(String key,
UserDetailsService userDetailsService,
MDJdbcTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
setParameter("remember-me");// parameter name in login form
this.tokenRepository = tokenRepository;
}
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
cancelCookie(request, response);
if (authentication != null) {
String rememberMeCookie = extractRememberMeCookie(request);
if(rememberMeCookie != null && rememberMeCookie.length() != 0) {
String[] cookieTokens = decodeCookie(rememberMeCookie);
if (cookieTokens.length == 2) {
String series = cookieTokens[0];
//remove by series
tokenRepository.removeTokenBySeries(series);
}
}
}
}
}
3) add beans
private static String key = "your string here";
#Bean
public MDJdbcTokenRepository persistentTokenRepository() {
MDJdbcTokenRepository db = new MDJdbcTokenRepository();
db.setDataSource(dataSource());
return db;
}
#Bean
public RememberMeServices rememberMeServices() throws Exception {
return new MDRememberMeServices(key, userDetailsService, persistentTokenRepository());
}
4) change your security spring config:
and().rememberMe().key(key).rememberMeServices(rememberMeServices())
The easiest solution is to change the Authentication object you use in such a way that calls to the getName() return "deviceID"+"username" instead of "username".
You could for example hash HTTP header UserAgent and use it as a device ID. The call to PersistentTokenRepository.removeUserTokens will then always only remove tokens related to the particular device.
The value returned from the getName() call can be customized by changing logic in your UserDetailsService implementation, as the Authentication.getName() typically delegates to UserDetails.getUsername() of the object returned from the UserDetailsService.
Other possible solutions will most likely require custom implementation of the org.springframework.security.web.authentication.RememberMeServices interface.
I am also a newbie in this world of Spring but I hope I can give you a tip...
Taking a look at PersistentTokenRepository you can notice that the only way to get the remember-me token is by using the seriesId stored in the cookie of your local browser. That way, when you log out on that device you only remove the "token) (actually the entire entry) of the corresponding seriesId.
So my try is to recommend you to just log out your user in order to delete its associated token.
I really hope that helps!
Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}