I would like to encapsulate Apache Shiro in a Servlet environment. I want to create MySecurityUtils and use Shiro SecurityUtils.getSubject in a static method. My question is whether this is a correct way to use SecurityUtils.getSubject method in a static method. Can this cause any problems in multithreaded servlet environment?
MySecurityUtils.java
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
public class MySecurityUtils {
public static MyUser getUser() {
Subject currentUser = SecurityUtils.getSubject();
MyUser myUser = new MyUser(currentUser);
...
}
}
MyUser.java
public class MyUser {
// ... constructors
public boolean isPermitted(..) {subject.isPermitted(...)}
}
I don't see why you would want to do this, but for your question's sake, this would be fine.
In a web context, Shiro's SecurityUtils#getSubject() returns a different Subject instance per request. Obviously if the subject is logged in, the credentials will be copied over (from session) to the new Subject instance. You are pretty much doing the same thing by returning a new MyUser instance on each call to getUser().
Careful though, if you call getUser() twice in the same request, you will get a different MyUser instance. However, the internal Subject will be the same. It can be problematic if you are doing logic other than delegating in your MyUser class.
After feedback of Sotirios I changed my code as follows
public class SecurityHelper {
public static boolean isAuthenticated(){
Subject currentUser = SecurityUtils.getSubject();
return currentUser.isAuthenticated();
}
public static void checkPermission(String permissionCode){
Subject currentUser = SecurityUtils.getSubject();
currentUser.checkPermission(permissionCode);
}
public static void checkPermission(String... permissionCodes){
Subject currentUser = SecurityUtils.getSubject();
currentUser.checkPermissions(permissionCodes);
}
... and so on
I encapsulate all application logic in a Helper class.
Related
The question is a little bit longer than expected. Below is the link to a similar one (3rd post) where I didn't find the answer satisfying.
TL;DR
I am trying to logout using the JAAS Login Module. Here is the brief structure of the project:
LoginService is responsible for instantiating LoginContext when a user wants to log in:
#Service
public class LoginService {
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
The LoginController calls the method:
#RestController
#RequestMapping("/login")
public class LoginController {
private final LoginService loginService;
#Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
#PostMapping
public ResponseEntity<UserDTO> getUserDTOFrom(#Valid #RequestBody Credentials credentials) {
UserDTO userDTO = loginService.getUserDTOFrom(userForm);
// return response that depends on outcome in the login service
}
}
The issue arises when I want to logout previously logged in user. LoginContext is responsible for calling the logout method in the JAAS Login Module. For instance:
loginContext.logout();
The method in the JAAS Login Module:
public class JAASLoginModule implements LoginModule {
#Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
I don't have the LoginContext in LogoutService and unable to completely clear the previously authenticated subject.
I tried to create a singleton bean to get the same instance of the LoginContext:
#Configuration
public class LoginContextBean {
#Lazy
#Bean
public LoginContext getLoginContext(Credentials credentials) throws LoginException {
System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
return new LoginContext("Login", new JAASCallbackHandler(credentials));
}
}
#Service
public class LoginService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = loginContextProvider.getObject(credentials);
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
}
#Service
public class LogoutService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public void performLogout() {
LoginContext loginContext = loginContextProvider.getObject();
try {
loginContext.logout();
} catch (LoginException e) {
LOGGER.error("Failed to logout: {}.", e.getMessage());
}
}
}
The solution is not particularly useful, since next / the same user to log in will get NPE on the LoginContext.
I read that HttpServletRequest's getSession().invalidate(); suppose to call the logout() of JAAS or that HttpServletRequest's logout() would do the job. But both methods have no effect. For instance:
#RestController
#RequestMapping("/logout")
public class LogoutController {
private final LogoutService logoutService;
#Autowired
public LogoutController(LogoutService logoutService) {
this.logoutService = logoutService;
}
#DeleteMapping
public ResponseEntity<Void> deleteJwt(#CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
request.getSession().invalidate(); // logout() is not called.
request.logout(); // logout() is not called.
return getResponse();
}
}
I want to get the hand on the previously created LoginContext when a user wants to log out but create a new one when another user tries to log in.
Please note that I am not using Spring Security.
EDIT:
One of the ideas was to use a singleton that will hold a Set of login contexts associated with the particular user. And then call and destroy them when the user logs out. A key for such a Set could be a JWT token or user id. After further thinking, it appeared to me that a user might have multiple sessions, and in this case, user id as a key will fail to serve its purpose. The second option is a JWT token, but there is a scenario when the future middleware will issue a new JWT token upon expiration, then my Set will have no way to return a valid login context.
After some research, my team decided that JAAS doesn't suit our needs. We are not using the complete functionality it has to offer, and it ties our hands rather than smoothing the developing process.
If you will encounter a similar issue, here is an explanation:
we are using WebSphere 8.5.5 that has the support of JAAS. It is possible to logout, but the price will be tying it to the application server. Considering that in our plans is to move from WebSphere, this implementation is not an option.
The link to one of such guides lies here.
There are two alternatives for the future:
Wrap it in Spring Security since it offers support for JAAS;
Replace the custom module entirely relying on Spring Security's
functionality.
I've just created my own custom authentication on my google app engine Java app. And it wasn't that much of a trouble as is the next thing I'm trying to do.
Authentication works fine but now I'm trying to add some additional fields to the default User object so that I wouldn't have to make so many calls to the server.
So what I've done so far is created a custom class that implements Authenticator. Based on whether the user is authenticated or not the authenticate method returns the User object or null. User object is then accessible to my API endpoints.
To extend my app functionality I've tried extending the default User object, making some new fields, and then passing it to endpoints. However, since the User object accessible by endpoints is not the same kind as the one I extended from I can't get the extra fields.
MyAuthenticator.java
import com.google.api.server.spi.auth.common.User;
public class MyAuthenticator implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
// some code
return new AuthUser(...)
}
AuthUser.java
import com.google.api.server.spi.auth.common.User;
public class AuthUser extends User {
private String newToken;
public AuthUser(String email) {
super(email);
}
public AuthUser(String id, String email) {
super(id, email);
}
public AuthUser(String id, String email, String newToken) {
super(id, email);
this.newToken = newToken;
}
public String getNewToken() {
return newToken;
}
}
UserEndpoint.java
import com.google.appengine.api.users.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
// ...
}
Notice different class imports.
I can't use AuthUser in UserEndpoint sth method because then API expects me to post that object with my call to server.
How can I pass extra data from authenticator to my endpoint method?
AppEngine docs say the injected types are the following:
com.google.appengine.api.users.User
javax.servlet.http.HttpServletRequest
javax.servlet.ServletContext
However, it doesn't mention com.google.api.server.spi.auth.common.User, but it works for sure. I just tried with AppEngine Java SDK 1.9.32. I don't know if it's a bug or feature.
So in UserEndpoint.java, you have to import com.google.api.server.spi.auth.common.User, then you can cast it to AuthUser.
import com.google.api.server.spi.auth.common.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
((AuthUser)user).getNewToken();
// ...
}
UserService.java
...
public class UserService {
public static User getUser(String username, String password) {
...
}
public static User getUser2(String username, String password) {
...
}
}
login.mxml
private function loginUser() : void {
lostPassword = false;
// this works fine
UserService.getUser(username.text, password.text);
// this fails !
UserService.getUser2(username.text, password.text);
}
getUser was already in UserService.java. I just created getUser2 and it's identical to getUser. When I try to call getUser2, i get the "Cannot invoke method" error.
question: Do I need to specify getUser2 in some other file? like in some configuration file? if so, which one and how do I do it.
Thanks.
think problem is JAVA static method according to Remoting Service definition
The Remoting Service lets a client application access the methods of server-side Java objects
In java/oops static methods are not associated to Object/instance its depends-upon/associated to class
your method should be like this to accept call from flex
public class UserService {
public User getUser(String username, String password) {
...
}
public User getUser2(String username, String password) {
...
}
}
Hopes that helps
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).
I've went thru Spring documentation and source code and still haven't found answer to my question.
I have these classes in my domain model and want to use them as backing form objects in spring-mvc.
public abstract class Credentials {
private Long id;
....
}
public class UserPasswordCredentials extends Credentials {
private String username;
private String password;
....
}
public class UserAccount {
private Long id;
private String name;
private Credentials credentials;
....
}
My controller:
#Controller
public class UserAccountController
{
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid UserAccount account)
{
//persist in DB
return account.id;
}
#RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
public String listAccounts()
{
//get all accounts from DB
return "views/list_accounts";
}
....
}
On UI I have dynamic form for the different credential types. My POST request usually looks like:
name name
credentials_type user_name
credentials.password password
credentials.username username
Following exception is thrown if I try to submit request to the server :
org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)
My initial thought was to use #ModelAttribute
#ModelAttribute
public PublisherAccount prepareUserAccountBean(#RequestParam("credentials_type") String credentialsType){
UserAccount userAccount = new PublisherAccount();
Class credClass = //figure out correct credentials class;
userAccount.setCredentials(BeanUtils.instantiate(credClass));
return userAccount;
}
Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.
One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.
Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?
Thank you!!!!!
I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.
An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.
So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :
Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).
I'm not sure, but you should be using ViewModel classes on your controllers instead of Domain Objects. Then, inside your saveAccount method you would validate this ViewModel and if everything goes right, you map it into your Domain Model and persist it.
By doing so, you have another advantage. If you add any other property to your domain UserAccount class, e.g: private bool isAdmin. If your web user send you a POST parameter with isAdmin=true that would be bind to user Domain Class and persisted.
Well, this is the way I'd do:
public class NewUserAccount {
private String name;
private String username;
private String password;
}
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid NewUserAccount account)
{
//...
}