Is there a way to access HttpSession from WebApplicationContext in a custom DataSource? I implemented a custom authentication processing filter that stores some infromation in the HttpSession. This information is then used by a DataSource to obtain a database connection.
Another option is to use the SecurityContextHolder to obtain an authentication token which is customized to include additional attributes. I am not sure this is the right approach.
This is what I have so far:
public class CustomDataSource extends DriverManagerDataSource implements ApplicationContextAware {
protected Connection getConnectionFromDriverManager(String url,
Properties props) throws SQLException {
// want to use the web context to get the http session
// Authentication has a getAttribute(String name) method
SecurityContext securityContext = SecurityContextHolder.getContext();
CustomAuthenticationToken authentication = (CustomAuthenticationToken) securityContext.getAuthentication();
Object attribute = authentication.getAttribute("db");
// create a connection object here
Object conn = getConnectionFromAttribute(attribute);
return (Connection)conn;
}
private WebApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = (WebApplicationContext)applicationContext;
}
}
Update:
I defined a new class called AuthInfo, which has username and password only. Then reated a ThreadLocal as a static final variable of a utility interface:
public interface WebUtils{
public static final ThreadLocal<AuthInfo> authInfo = new ThreadLocal<AuthInfo>();
}
The value of the ThreadLocal is then set in the attemptAuthentication method of the filter
AuthInfo info = new AuthInfo();
info.setName(username);
info.setPass(password);
WebAttributes.authInfo.set(info);
Now, in the custom DataSource
protected Connection getConnectionFromDriverManager(String url,
Properties props) throws SQLException {
AuthInfo info = WebAttributes.authInfo.get();
Connection conn = getConnFromAuthInfo(info);
return conn;
}
Isn't this is the same as using the SecurityContextHolder and the CustomAuthenticationToken?
In a typical multilayer application, your data access layer shouldn't have any knowledge of higher layers such as the HTTP interface.
I suggest you investigate using Spring's session or request scoping. You can create a scoped proxy bean in one of these scopes, place the authentication information in that, and inject it into the datasource.
Don't place that logic in the datasource. The http session and the database should not be related.
You can make the check in a HandlerInterceptor or with aspectj, around your controllers or service layer
Related
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
I have used below codes for fetching session attributes from session util class(contains static methods). Want to know that below code could be right approach in multithreaded environment or is there any better other ways for fetching sessions from util.
public static HttpSession getSession(){
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession();
return session;
}
public static String getEmailIDFromSession(){
return (String)getSession().getAttribute("USER_EMAIL");
}
No, it's OK. And it is really thread safe. But from other side you should understand that it will be available only within HTTP Request Thread and from Spring MVC environment.
From other side, if you want to get that attribute from your #Controller or #Service you always can inject session there:
#Controller
class MyController {
#Autowired
private HttpSession session;
}
Right now, I'm learning about implementing REST API with a Spring Security Framework.
My question is, after success login with spring security, how can i send the request to server and make sure the server know that i am have been authorized (already login with success)?
I have a some experiment code to do testing
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { WebAppConfig.class, SecurityConfig.class })
public class TheTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy filterChainProxy;
protected MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders//
.webAppContextSetup(wac)//
.addFilter(filterChainProxy)//
.build()//
;
}
#Test
public void testDoingArequest() throws Exception {
// login here
HttpSession session = mockMvc.perform(//
//
post("/login-process")//
.param("username", "theusername")//
.param("password", "thepassword")//
)//
.andDo(print())//
.andExpect(status().isFound())//
.andReturn().getRequest().getSession()//
;
// login is success and now trying to call request
this.mockMvc.perform(//
get("/doingRequest")//
.session((MockHttpSession) session)// <-- where this part must added to?
)//
.andExpect(status().isOk())//
.andDo(print())//
;
}
}
-
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//
.antMatchers("/doingRequest").authenticated()//
.anyRequest().permitAll()//
.and()//
.csrf().disable()//
.formLogin()//
.loginPage("/")//
.loginProcessingUrl("/login-process")//
.defaultSuccessUrl("/");
}
-
#Controller
public class TheController {
#RequestMapping(value = "doingRequest", method = RequestMethod.GET)
#ResponseBody
public String doingSomething() {
return "Only authorized user can read this";
}
}
-
Above code is running well but i dont know how to implementing the "session" part in HTTP. I'm expecting something like put a token or something in header or url in real life application/implementation not in the testing environment. How the client get the token? How do we call the request (with token embedd) in client code.?
Are you looking for mocking a session object.If yes then you need to import the mock session object, and in the test class you can create and use the object.
import org.springframework.mock.web.MockHttpSession;
MockHttpSession session = new MockHttpSession();
session.setAttribute("variable", object);
The configuration you have will use the server side session to maintain the security context, and the link with the client is the standard servlet JSESSIONID cookie, so this has nothing to do with Spring Security. Whether you actually want a session or not will depend on the nature of your client. If there is no state maintained between the client and server, then each request from the client must be separately authenticated/authorized. This might be done using Basic authentication for example, or something like an OAuth2 access token depending on your requirements.
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
}
Hi
I am currently playing with Guice and #SessionScoped. To give it more sense, I decided to build a (very simple) authentication process.
Below, I will explain each step I have done. Then I will ask you some questions.
[1] I have create an Identity class which represents a person (guest or user) :
#SessionScoped
public class Identity implements Serializable
{
private String uid;
private String name;
public boolean isAuthenticate()
{
return uid != null;
}
public void logout()
{
this.uid = null;
}
/*Setters-Getters*/
}
[2] Next, I created an Authentication class that log-in user:
public class Authentication
{
#Override
public Identity authenticate(String login, String password)
{
/*some code*/
Identity identity = new Identity();
identity.setUid(user.getId());
return identity;
}
}
[3] Then, in my Servlet, I log-in the user :
#RequestScoped
public class LoginAction
{
#Inject
Injector injector;
protected void login(HttpServletRequest req, HttpServletResponse resp)
{
Identity identity = injector.getInstance(Identity.class);
Authentication auth = new Authentication();
identity = auth.authenticate("login","password");
}
}
[4] Finally, I create a Filter that show me if user is authenticated :
#Singleton
public class SecurityFilter implements Filter
{
#Inject
private Injector injector;
#Override
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain)
{
Identity identity = injector.getInstance(Identity.class);
if(identity.isAuthenticate())
{
System.err.println("USER");
}
else
{
System.err.println("GUEST");
}
chain.doFilter(request, response);
}
}
Well, this code is not working. My Identity's uid is always "null".
Let's go for questions :
a - First of all, Why did my code not works ?
b - Is #SessionScoped equivalent to set the object in HttpSession ?
c - How to invalidate the Identity object (only it) in (http)session ?
d - Generally, In which case did we have to use #SessionScoped?
Thanks you for reading,
Waiting your answers.
[a] You're assigning a new instance of Identity to a local variable in LoginAction, not replacing the instance managed by Guice. You could solve the problem by populating the uid and name fields on the existing Identity instance managed by Guice.
For example, instead of
identity = auth.authenticate("login","password");
you could say:
Identity identity = injector.getInstance(Identity.class);
Authentication auth = new Authentication();
Identity authenticated = auth.authenticate("login","password");
identity.setUid(authenticated.getUid());
identity.setName(authenticated.getName());
There are cleaner ways to do it, but you get the idea.
[b]/[d] That's correct: #SessionScoped is equivalent to setting a variable in the HttpSession, and this is the kind of situation that you would use it. You'll need it for objects that need to be unique across sessions, but need to be available for every request.
[c] I'm not quite sure what you mean, but if you're wanting to redirect to different places in the app depending on whether the user is logged in, your filter design is a common way to do that.
Some improvements that you could make:
Have a SessionScoped service that manages the session's user's Identity, and make sure it's synchronized on the Identity instance. That way you won't have concurrency troubles if a user makes two requests in quick succession.
Prefer injecting Providers instead of injecting the Injector(examples here) to decouple your classes from Guice.
Inject dependencies into your classes' constructors, instead of injecting fields. This allows for easier testing (by providing mock/stub dependencies in tests).