Spring MVC session resource is getting shared by logged users - spring-mvc

I have deployed a web application using Apache tomcat 8, java 8 and centos server in production.
When i tested the system by 5-6 users concurrently in office network everything seemed ok. But in client network, one users info is getting by another user(session attributes are shared/mixup). For example, if user A logs in, after a while his name is showing user B, who is logged in from different computer. If user presses Ctrl+R then his/her previous session restores for a while.
N.B. this scenario never happens other than that client network. They are using specific proxy. Other than proxy, this scenario does not happen.
I have a simple LoginController class without defining any scope. Some code snippets are below:
1. Login GET method:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLogin(#ModelAttribute LoginForm loginForm)
{
return "login";
}
2. Login POST method:
#RequestMapping("/login", RequestMethod.POST)
public String Login(#ModelAttribute LoginForm loginForm, HttpSession session)
{
User dbUser = this.userService.getUser(loginForm.getUserID());
if (dbUser != null)
{
if(passwordCheckedSuccess(dbUser.getPassword(), loginForm.getPassword()))
{
session.setAttribute("userName", dbUser.getUserName());
session.setAttribute("userId", dbUser.getUserId()); // primary key of user class
return "dashboard";
}
else
{
return "login";
}
}
}
3. I have created a loginIntercptor class to filter secured pages:
public class LoginInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (!request.getRequestURI().endsWith("/login"))
{
if (request.getSession().getAttribute("userId") == null)
{
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
return true;
}
}
I am not using spring security.
Please suggest a way to get rid of it. Thanks.

Related

Retrieving custom user roles after login - Blazor WASM

I have a Blazor WebAssembly (WASM) app that authenticates users using Okta. After they successfully log in via Okta, I want to authorize the user by calling an API that I wrote to retrieve that users roles and other general user info that we store. This call to get user info must also include the access token retrieved from the Okta log in.
The authentication piece with Okta works fine.
I'm not sure how to correctly call our API to get user info/roles as part of the login process, so that the roles can be added as claims BEFORE being redirected to any other page.
The Okta log in piece is set up using the RemoteAuthenticatorView and added in Program.Main as:
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = builder.Configuration.GetValue<string>("Okta:Authority");
options.ProviderOptions.ClientId = builder.Configuration.GetValue<string>("Okta:ClientId");
options.ProviderOptions.ResponseType = "code";
});
What I've tried so far:
Using the OnLogInSucceeded event callback of the RemoteAuthenticatorView. This doesn't work because the user will be redirected to the page they tried to access before the api call completes. Therefore if the page has any Authorize(Roles="Admin") type of restrictions on it, those roles haven't been populated yet.
Using a factory that inherits from AccountClaimsPrincipalFactory. This seems like the correct way, however I'm getting runtime errors anytime I inject certain classes or services into my factory. I think I've narrowed it down to being an issue with an injected service using the IHttpClientFactory. Here's my factory code:
public class ClaimsPrincipalFactory : AccountClaimsPrincipalFactory
{
private IUserService userService { get; set; }
public ClaimsPrincipalFactory(
IAccessTokenProviderAccessor accessor,
IUserService userService
)
: base(accessor)
{
this.userService = userService;
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
var userInfo = await userService.UserInfo();
var identity = user.Identity as ClaimsIdentity;
if (userInfo != null)
{
foreach (var role in userInfo.UserRoles)
{
identity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, role.ApplicationRole.Name));
}
}
return user;
}
}
Here is the constructor of my UserService:
public UserService(IHttpClientFactory clientFactory)
{
http = clientFactory.CreateClient("BlazorClient.ServerApi");
}
The CreateClient line causes this runtime error:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: ValueFactory attempted to access the Value property of this instance.
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
at System.Lazy`1[[Microsoft.Extensions.Http.ActiveHandlerTrackingEntry, Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1[[Microsoft.Extensions.Http.ActiveHandlerTrackingEntry, Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
Here is how the httpFactory is set up in my Program file:
builder.Services
.AddHttpClient("BlazorClient.ServerApi", client => client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("ServerApi:BaseAddress")))
.AddHttpMessageHandler<CorsRequestAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorClient.ServerApi"));
Here is how the Factory is added in Program:
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<ClaimsPrincipalFactory>();
What is the correct way of doing this? I've been stuck on this issue for literally days and it doesn't seem like it should be this hard (and so hard to find documented info on it).
I was strugling with the same issue and based on your code snippet I might solved it.
What I did is to pass a HttpClientFactory to the generator of the CustomUserFactory, then in the CreateUser func I can create my userService with this factory.
Hope it's an ok solution and helps you as well.
public class CustomUserFactory : AccountClaimsPrincipalFactory<CustomUserAccount>
{
private IUserService _userService { get; set; }
private IHttpClientFactory _httpClientFactory { get; set; }
public CustomUserFactory(IAccessTokenProviderAccessor accessor, IHttpClientFactory httpClientFactory)
: base(accessor)
{
_httpClientFactory = httpClientFactory;
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
_userService = new UserService(_httpClientFactory);
...

spring-boot security identify by token

I got an app and I wanna create a connection to my rest-api.
Each user will get a "token" which will automatically be refreshed by google and co. In my requests, I will send the token and if it can be resolved to the user, the request should be answered, else if it is not up to date, I just wanna drop the request and return an error.
Are there still some possibilities?
Thanks for your help!
Current starting:
https://gist.github.com/PascalKu/97bca9506ad4f31c9e13f8fe8973d75b
You need to implement custom authentication in spring. I did the same thing but I had a db like:
fb_email_address | user_id | other_fields...
You must create these classes:
#Component
class TokenAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) {
String theToken = request.getParameter('theToken');
TokenAuthentication tokenAuth = new TokenAuthentication(theToken)
SecurityContextHolder.getContext().setAuthentication(tokenAuth)
}
}
You need to add the authentication provider to spring's security system:
#Configuration
#EnableWebSecurity
class WebConfigHolder extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
#Autowired private TokenAuthenticationProvider tokenAuthenticationProvider
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider)
}
}
Implement authentication provider which actually checks to see if the token is valid.
#Component
class TokenAuthenticationProvider implements AuthenticationProvider {
//called by provider manager at some point during execution of security filters, I think
//it's the security api's job to call this
//the fbauthentication we create in our fbauthenticationfilter gets passed into this
#Override
#Transactional
Authentication authenticate(Authentication auth) {
TokenAuthentication tokenAuthentication = (TokenAuthentication) auth;
String theToken = auth.getThetoken();
boolean theTokenIsInDB = ///CHECK TO SEE IF TOKEN IS IN DB
if(theTokenIsInDB) {
TokenAuthentication t = new TokenAuthentication();
t.setAuthenticated(true);
return t;
} else {
throw new BadCredentialsException("Could not find user");
}
}
#Override
boolean supports(Class<?> authentication) {
boolean ret = TokenAuthentication.isAssignableFrom(authentication)
return TokenAuthentication.isAssignableFrom(authentication)
}
}
You need a simple Authentication Class that is just the object that's used to store the credentials while spring is waiting for the thread to get to the spring security filter; once it gets to that filter it passes authentication objects to the providers that support them. This allows you to have multiple authentication methods like FB, Google, custom tokens, etc... In my app I use FB tokens and in my provider, I check to see if the FB token corresponds to an authorized email address on my whitelist of email addresses. If it does, the user gets access to my app.
public class TokenAuthentication extends Authentication{
String token;
boolean isAuthenticated = false;
public TokenAuthentication(String theToken) { this.token = theToken;}
//getters and setters
}
What this code all does is, whenever someone accesses your API such as /api/person/get?theToken=132x8591dkkad8FjajamM9
The filter you created is run on every request. It checks to see if theToken was passed in and adds the TokenAuthentication to spring security.
At some point in the filter chain, spring security filter will run, and it will see that a TokenAuthentication has been created, and will search for a provider that can perform authentication on that. That happens to be your TokenAuthenticationProvider.
TokenAuthenticationProvider does the actual authentication. If it returns an authentication object that has isAuthenticated set to true, then the user will be allowed to access that api call.
Once authenticated, a user doesn't need to pass theToken again until his cookies are cleared or you invalidate his session. So he can call /api/person without the query parameters for the rest of his interactions. That's because the authentication is stored as a session-scoped data in spring.
Hope that helps. Let me know if anything's missing.

Remember Values Asp.Net

This is my controller code:
private string testVal;
public ActionResult Index()
{
testVal = "test";
return View();
}
public ActionResult NextView()
{
if (testVal == null)
Debug.WriteLine("testVal is null");
return View();
}
Is it possible to remeber values like testVal after changing page? It seems that when redirecting it resets values (testVal in NextVal is null).
Edit:
I try to save values to session but Session is null. I am using SignalR and when user is connected to page i use static event from hub to inform controller that user has connected - but inside method that runs on that event Session is unfortunetly null.
My controller code:
public ActionResult Index()
{
LoadingHub.userConnected += new EventHandler<IdEventArgs>(UserConnected);
return View();
}
private void UserConnected(object sender, IdEventArgs e)
{
Debug.WriteLine("User Connected with Id: " + e.Id);
if (Session == null)
Debug.WriteLine("Session is null");
}
My signalr hub:
public class LoadingHub : Hub
{
public static event EventHandler<IdEventArgs> userConnected;
//Function informs server that user has connected
public void Connected()
{
Debug.WriteLine("Hub Connected Method");
var id = Context.ConnectionId;
userConnected(this, new IdEventArgs(id));
}
}
Every time that you make a request a new instance of the controller is created so using a private field you will not be able to retain the value of this variable.
The easiest way for you to retain it it is to use a session. (if you want to retain this value per user base)
for example in your code
public ActionResult Index()
{
System.Web.HttpContext.Current.Session["testVal"] = "test";
return View();
}
public ActionResult NextView()
{
if (System.Web.HttpContext.Current.Session["testVal"] == null)
Debug.WriteLine("testVal is null");
return View();
}
you can use cookie or cache to replace the variable.
when you redirect to a webpage ,the controller will be newed ,so you cannot get the right testVal .but the cookie is stored in broswer .so you can set it and get .
You may use session or Pass the data to the controller
Have you looked into ASP.NET server side state management click here.
These are basically different ways to remember a value on the server once a new page has been loaded.
So a few server side techniques you could use to remember testVal are Session State or Application State. However Session State is more suitable for your scenario as it is only specific to the user's current session whereas Application State stores data that can be shared between sessions and would therefore be more ideal for global variables.
You can read the link I provided to read more on the differences though.
I would also like to warn you (as some say to use cookies), the user can delete or disable or manipulate them on the browser so this isn't an ideal solution.

Spring 3 MVC : move/share model from Controler to another Controler

I'm doing a little project with Spring 3 MVC & jQuery
I'm not sure how to ask it so i'll try to explain
I have this scenario :
LoginPage(with User object model) ---submit--> Server sends OK back to LoginPage -->
(LoginPage) redirect to Page2 using window.location = "Page2"
Problem : Page 2 doesn't recognize User
How to make it work? I tried reading about #SessionAttributes but didn't really understand it.
#Controller
public class LoginController {
...
...
#RequestMapping(value = "/")
public ModelAndView loginPage(ModelMap model) {
model.addAttribute("user", new User());
logger.info("Loading Login Page");
return new ModelAndView("login");
}
#RequestMapping(value = "/loginSubmit.html" ,method=RequestMethod.GET)
public String processSubmit( ModelMap model, User user) throws InterruptedException{
...
...
return "1" to login page
...
...
Here I want User user to be known from last controller,but it's making a new one instead.
#Controller
public class Controller2 {
#RequestMapping(value = "/home")
public String home(ModelMap model, User user) {
...
...
}
LoginPage.jsp
$.get("loginSubmit.html", form1Var.serialize(), function(data){
var isSucess = data.charAt(0) == "1" ? true : false;
if ( isSucess == true) {
alert("ok...");
window.location = "home";
}
EDIT Moved my solution to Answers.
By default the server side in Spring MMVC is stateless. To save state between requests you must put the data you want to save in the session. This data is then available for every request in the same session (i.e. from the same client).
In the solution you found, the #SessionAttributes("user") annotation has told Spring MVC to that you want the user object to be persisted across requests by saving it in the session. This is how Spring abstracts you from all the work of actually maintaining the state yourself.
My Solution :
#SessionAttributes("user")
on both controllers
and
#ModelAttribute("user") User user
as param in the method - worked
I'v also added
#ExceptionHandler(HttpSessionRequiredException.class)
public String SessionException(HttpSessionRequiredException ex) {
logger.info(ex.getMessage());
return "redirect:LogIn";
}
to catch Exception and the user will go to LoginPage instead of a exception error page
As Donal Boyle pointed , conclusion : use #SessionAttributes to share models between Controllers

Why am I getting 400 and 405 exceptions with my WCF RESTful client application with PUT and POST?

I am building an application that has a two server setup - a "services" server and a "front end" server. The "services" server has a WCF service. The "front end" server has a traditional ASP.NET web forms application that accesses the WCF service. I can get the GET requests to work fine. However I can't seem to get any PUT or POST requests to work. When I try to issue a POST I get a 400 exception - Bad Request. When I try to issue a PUT I get a 405 exception - Method Not Allowed. I feel like I have everything set up correctly - but obviously not. Here is the code for my WCF service:
Service.svc:
<%# ServiceHost Language="C#" Debug="true" Service="TestSvc" Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>
TestSvc.cs:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract]
public class TestSvc
{
[WebGet(UriTemplate="/")]
[OperationContract]
public Users GetUsers()
{ ...code to get all users from database }
[WebInvoke(UriTemplate = "/", Method = "POST")]
[OperationContract]
public void AddUser(User user)
{ ...code to add user to database }
[WebInvoke(UriTemplate = "/{id}", Method = "PUT")]
[OperationContract]
public void UpdateUser(string id, User user)
{ ...code to update user in database }
[WebGet(UriTemplate = "/{id}")]
[OperationContract]
public User GetUser(string id)
{ ...code to get a single user from database }
}
(In addition I have classes for the User and Users entities)
Then on the front end I have this interface:
[ServiceContract]
interface ITestService
{
[WebGet(UriTemplate = "/")]
[OperationContract]
Users GetUsers();
[WebInvoke(UriTemplate = "/", Method = "POST")]
[OperationContract]
void AddUser(User user);
[WebInvoke(UriTemplate = "/{id}", Method = "PUT")]
[OperationContract]
void UpdateUser(string id, User user);
[WebGet(UriTemplate = "/{id}")]
[OperationContract]
User GetUser(string id);
}
And then I have this helper class:
public class ServiceClient
{
WebChannelFactory<ITestService> cf;
ITestService channel;
public ServiceClient()
{
cf = new WebChannelFactory<ITestService>(new Uri("http://myserver/service.svc"));
channel = cf.CreateChannel();
}
public Users GetUsers()
{ return channel.GetUsers(); }
public User GetUser(string id)
{ return channel.GetUser(id); }
public void AddUser(User user)
{ channel.AddUser(user); }
public void UpdateUser(string id, User user)
{ channel.UpdateUser(id, user); }
}
And finally here is what the code behind looks like on my page that is trying to do an update of a User object.
protected void btnSave_Click(object sender, EventArgs e)
{
User _user = new User(Convert.ToInt32(txtID.Value), txtFirstName.Text, txtLastName.Text, Convert.ToInt32(txtAge.Text), chkIsRegistered.Checked);
ServiceClient _client = new ServiceClient();
_client.UpdateUser(txtID.Value, _user);
Response.Redirect("~/ViewUsers.aspx");
}
When I run the front end project and try to do an update I get the following error:
The remote server returned an unexpected response: (405) Method Not Allowed.
Any ideas? Thanks, Corey
How much data are you sending? I have had trouble with WCF services when sending more than 64k at a time (with the default configuration, you can increase it). POST would typically return a 400 in this case, but I don't know what PUT would return, since my service doesn't use PUT.
I solved part of the problem. I deleted the WebDav module in IIS for the site and then the PUT began to work. (I think it has to do with WebDav handling the PUT and DELETE verbs) However the POST verb is not working. It is returning a 400 Bad Request error. I will try solving that in a separate question.

Resources