I have a Spring bean that needs information from the request, but isn't directly called from the controller (although it could be - but I'd like to try this without it)
Basically, my API makes requests to other services over thrift. When it makes the request, there's a service call like this:
authenticationService.authenticate(null, "username", "password");
The first parameter (the null) is usually a "placeholder" instance of a request context. The request context contains information about the user making the request, the originating IP, etc. This way, I get all of the details about the original caller without letting my API infrastructure leak into the backend.
However, to do this, I have an InvocationHandler that intercepts method calls made against a proxy of my service interfaces. Inside of that proxy handler, I have a RequestContextFactory wired in that creates instances of a RequestContext. Inside of this factory, I need to get information from the request. Particularly, the SecurityContext, so I can identify the user making the call.
Right now, I have:
#Provider
#Component
public class WebRequestContextFactory implements RequestContextFactory {
#Context private ContainerRequest containerRequest;
public RequestContext createRequestContext() {
}
}
Unfortunately, containerRequest is always null.
You can use ServletRequestAttributes to get the information from the request and the ServletRequestAttributes can be obtained from RequestContextHolder:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes();
If the request is processed by the Spring DispatcherServlet, there is no need of any special setup. DispatcherServlet already expose all relevant state. But if the requests are processed outside of Spring's DispatcherServlet, then you need to add javax.servlet.ServletRequestListener in your application's web.xml file:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
This will associate the request with the current thread and the associated request attributes can then be retrieved via RequestContextHolder.
In a slightly different scenario, the following worked for me. YMMV.
Replace this:
#Context private ContainerRequest containerRequest;
With this:
#Context private javax.inject.Provider<ContainerRequest> containerRequestProvider;
Then at the place in your code where you need the ContainerRequest:
ContainerRequest containerRequest = containerRequestProvider.get();
Code sample that pointed me toward this solution: https://github.com/psamsotha/jersey-okhttp-interceptor-demo/blob/b3b2da00b284e75011ea780fb37b555ea581ac96/src/main/java/com/example/stackoverflow/client/UserFactory.java
For Authentication, it's better to use container realm, or to use normal servlet.
And for Authorization, you can use Application or rest servlet. And in this kind of process, you can find the info from context annotation. Here's sample:
(#Context final SecurityContext sc, #Context Request request) {
logMe(sc);
...
}
private void logMe(final SecurityContext sc) {
try {
LOGGER.info("User=" + sc.getUserPrincipal().getName());
LOGGER.info("User Role?=" + sc.isUserInRole("user"));
LOGGER.info("Auth way=" + sc.getAuthenticationScheme());
} catch (final Exception e) {
LOGGER.debug(e);
}
}
Or:
(#Context final SecurityContext sc, #Context ContainerRequestContext request) {
...
You can create a access token that contains your desire information such as IP Address, user name etc. During the authentication phase create a custom token and put this token into spring security context. Later you could extract this token from other places such in your proxy classes. After extracting the token you validate or whatever you want.
Creating custom object and token:
public class CustomAuthentication {
private String userId;
private String password;
private String ipAddress;
}
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private CustomAuthentication customAuthentication;
public CustomAuthenticationToken(MobiLabAuthentication authentication,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.customAuthentication = authentication;
setAuthenticated(true);
}
public CustomAuthenticationToken() {
super(null);
setAuthenticated(false);
}
#Override
public Object getCredentials() {
return customAuthentication.getPassword();
}
#Override
public Object getPrincipal() {
return customAuthentication.getUserId();
}
}
Store the token into Spring security context
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new RestUserAuthrity("YOUR_APP_ROLE"));
//Extract IP , user and pass etc and construct CustomAuthentication instance
CustomAuthentication authentication = new CustomAuthentication(.....)
CustomAuthenticationToken authenticationToken = new CustomAuthenticationToken(
authentication, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
Validate security information from the Proxy bean
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
if (authentication instanceof CustomAuthenticationToken) {
CustomAuthenticationToken token = (CustomAuthenticationToken) authentication;
//now you can get your ip address from token
}
Related
I have a spring boot microservice application. I have a requirement such that,
If method is called through controller (i.e. some user hit API), response should be the username of logged in user.
If method is called from any non-controller like scheduled task, Async method etc. response should be default user i.e. System.
So, I have a UserDetailService method where I want to write this logic.
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
HttpServletRequest request;
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser()
{
if (request == null)
{
UserReturnData userDetails = new UserReturnData();
userDetails.setId((long) 404);
userDetails.setUsername("system");
return userDetails;
} else
{
UserReturnData userDetails = webClientBuilder.build().get().uri(reqUrl + "user/me")
.header("Authorization", request.getHeader("Authorization")).retrieve()
.bodyToMono(UserReturnData.class).block();
return userDetails;
}
}
}
To check if method is triggered from controller, I auto-wired HttpServletRequest and compared it to null.
However, when this is triggered from Async service, its throwing exception "Are you referring to request attributes outside of an actual web request"
Is there any other way to identify where the method is getting triggered from. Logic is simple, if controller is involved i.e. if some API call is involved, return logged in user else return default user.
SecurityContextHolder can be used to check if the current thread is associated with a logged in user. Following method demonstrates the use.
public String getCurrentUser() {
String userName="SYSTEM";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication!=null && !(authentication instanceof AnonymousAuthenticationToken)) {
userName = authentication.getName();
}
return userName;
}
Contributed by #Sridhar Patnaik. Taking reference from R.G.'s comment, handled it in below way
Removed autowired HttpServletRequest
Created request variable
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
Added null check to request. So, if controller is involved, request will be non-null and logged in user will be returned. Else, system user will be returned
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
/*
* #Autowired HttpServletRequest request;
*/
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser() throws Exception
{
try
{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
if (request == null)
{
UserReturnData userDetails = new UserReturnData();
userDetails.setId((long) 404);
userDetails.setUsername("system");
return userDetails;
}
else
{
UserReturnData userDetails = webClientBuilder.build().get().uri(reqUrl + "user/me")
.header("Authorization", request.getHeader("Authorization")).retrieve()
.bodyToMono(UserReturnData.class).block();
return userDetails;
}
} catch (Exception e)
{
throw new Exception("Something Went Wrong !");
}}
The proposed approach is sensible, injecting a request-scoped HttpServletRequest bean into the singleton service bean will work. See the answer to Access a request scoped bean in servce for more details.
As other answers have suggested, you can also use the security context to determine if the current thread is associated with a logged in user. However, that's not sufficient to identify whether the method was called in the context of an HTTP request, as opposed to some other non-HTTP request context (e.g. scheduled task). HTTP requests can be made by anonymous or unauthenticated users, if your Spring Security configuration permits it.
I am working on a Spring Boot RESTful application which will be exposing a bunch of APIs for the web app to perform CRUD operations on the resources.
I am using spring-data-rest (along with spring-data-jpa of course) to expose the entities/repositories with the help of Spring Magic.
Even though I have secured (role-based) the endpoints with spring-security, it is not completely secure.
For example:
I have a User entity with has one-to-many relationship with Car. So the endpoint (auto exposed by spring-data-rest) for getting a user's cars is localhost:8080/users/{userId}/cars
However, any user with the required role can just pass the userId of another user and still access the endpoint.
The behavior I want is to secure these endpoints in a way that if I a logged-in user's ID is 1, then we can only hit localhost:8080/users/1/cars. Any other request with any other userId should end up in 403 or something.
Note: I know if write my own controllers then I can get a handle of the principal and do what I desire. I just want to know is there a way or pattern in spring-data-rest to achieve this?
Since you have already secured the application with Spring Security , here is another alternative with Method Security Expressions
Please review the #Pre and #Post Annotations for your requirement.
You may store the logged-in user's userId to the Authentication object.Details here.
Secure the required method with the #PreAuthorize annotation as follows
#PreAuthorize("#user.userId == authentication.principal.userId")
public List<Car> getCars(User user){..}
Do remember to enable method security
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {..}
To achieve that you need to write an Interceptor.It will be used under following situation:
Before sending the request to the controller
Before sending the response to the client
Before writing any Interceptor it should implement the HandlerInterceptor interface.
Three methods Interceptor supports are :
preHandle() method − Perform operations before sending the request
to the controller. This method should return true to return the
response to the client.
postHandle() method − Used to perform operations before sending
the response to the client.
afterCompletion() method − This is used to perform operations
after completing the request and response.
Code :
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String pathVariablesMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
//From this pathVariablesMap extract UserId and match with a loggedinUserId
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
) throws Exception {}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {}
}
By using a InterceptorRegistry you can register your Interceptors like below :
#Component
public class MyRegistoryConfig extends WebMvcConfigurer{
#Autowired
MyInterceptor myInterceptor ;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor );
}
}
For more Info follow this link Interceptors
EDIT : As #Ritesh suggested added that point.
You're using spring security(great :D), so it's better to create a simple filter, register it, then simply do your custom authorize in that filter.
In brief
Create a Custom filter
Get userId from the URL path
Get userId from SecurityContextHolder (Authenticated user principal)
Compare fetched userIds
Register filter in spring security config (After BasicAuthenticationFilter)
1- Create a custom filter (Pseudo-code)
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//Fetch userId from path
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getContextPath();
//..
//Fetch userId from SecurityContextHolder (User Principal)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Long userId = user.getId();
//Compare userId (fethced from path) with authenticated userId (fetched from principal)
//If comparison is ok
chain.doFilter(request, response);
//else
//throw Unauthorize
}
2- Register a filter in spring security config (After BasicAuthenticationFilter.class)
#Configuration
public class Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
}
}
With this structure when an authenticated user sends a request, the request will be first checked (Comparison between userIds) and then sent.
More information for creating a filter in spring security:
A Custom Filter in the Spring Security Filter Chain
I use Spring Security, and I found strange behavior of framework while login. Spring Security WebAuthenticationDetails has parameter sessionId which is getting from HTTP request, and it all should be good, but in fact REST request gives me another session id. If I will autowire HttpSession and then get session id from it, I will get Spring-like id. So it seems that I have two ids for one user. Is it correct? Or I missed something?
EDITED:
For example this class will gave some session id
public class AuthenticationEventListener implements ApplicationListener<AbstractAuthenticationEvent> {
#Autowired
HttpSession httpSession;
#Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
if (event instanceof AuthenticationSuccessEvent) {
LoggedUser loggedUser = (LoggedUser) event.getAuthentication().getPrincipal();
loggedUser.initSessionParams(event.getAuthentication());
String sessionId = httpSession.getId();
}
}
}
and this method will give another one:
#RequestMapping(value = "/chart")
public Map getTestStatusesChart(HttpServletRequest request) {
String sessionId= request.getSession(false).getId();
return null;
}
So the answer is next: with condition of security Spring change session id by default. To prevent such behavior you need to disable session-fixation-protection in Spring Security config. more info by link
Looking to protect pages in a basic java spring application based on a token. After the token is consumed I would need the application to know the token was valid at some point and then put some time to live on that session. Below is the controller I have to consume the token.
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(ModelMap model, #RequestParam(value = "token", required = false) String token) {
if(token==null) {
return "redirect:403";
} else if(token.isEmpty()) {
return "redirect:403";
} else {
//perform token WS call to validate the token
return "redirect:home";
}
}
#RequestMapping(value = "/403", method = RequestMethod.GET)
public ModelAndView accesssDenied(Principal user) {
ModelAndView model = new ModelAndView();
model.addObject("msg",
"You do not have permission to access this page!");
model.setViewName("403");
return model;
}
After performing some check on the token how can I protect all of the subsequent pages? Id also like to be able to secure api calls as well. Can anyone point me in the direction of the spring component?
I think you should take a look at Spring Security instead of rolling your own solution - it is built for handling authentication.
What you especially should look at is session management which sounds like what you're trying to do here.
Depending on how your users get their token you might have to implement your own authentication manager and/or login flow, though the default ones cover a lot of common cases too.
Once you have Spring Security set up and your session management working you would protect URLs either by annotating the controller methods:
#RequestMapping("/api/protected")
#PreAuthorize("hasRole('ROLE_USER')")
public String myProtectedController(Authentication authentication, Model model) {
// User will be authenticated here
}
or by registering them into the HTTP security configuration:
#Configuration
public class SecurityConfig extends WebSecurityConfigurationAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// Everyone can acess /login
.antMatchers("/login").permitAll()
// Only authorized users can access URLs under /api/
.antMatchers("/api/**").access("hasRole('ROLE_USER')")
}
}
Of course, in your case you might use something other than ROLE_USER since you may or may not have actual users but something else in your session that you can use.
I am new to Spring Web MVC..
Can I get some example or online link that shows me how to implement logout feature using spring web mvc ?
I don't want to use the in built feature of spring security (i.e. ACEGI)..
Thanks in advance...
The trick with the session invalidation doesn't work. It seems the Spring authentication buffers the session ID somewhere and accept the COOKIE even, if the session was invalidated.
Another solution is to clear the Spring security context manually:
public void manualLogout() {
SecurityContextHolder.getContext().setAuthentication(null);
}
Here is the code, how to log in user manually (if somebody needs):
public void doManualLogin(HttpServletRequest request, String u, String p) {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(u, p);
token.setDetails(new WebAuthenticationDetails(request));
Authentication auth = authenticationProvider.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
where the authenticationProvider is the bean from you spring configuration which implements
org.springframework.security.authentication.AuthenticationProvider
You only have to invalidate the session and the user is logged out. This is directly supported by the servlet api: HttpSession.invalidate(). You can write one controller that does only call invalidate.
class Logout implements Controller{
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response){
ModelAndView view = //?;
request.getSession().invalidate();
return view;
}
}
#Controller
public class LogoutController {
#RequestMapping(value="/logout",method = RequestMethod.GET)
public String logout(HttpServletRequest request){
HttpSession httpSession = request.getSession();
httpSession.invalidate();
return "redirect:/";
}
}
Please use above code to implement logout filter