Currently in most of my controller classes i check whether the session exists for each mapping.
For example:
#Controller
#RequestMapping("/admin")
public class AdminController{
#GetMapping
public ModelAndView admin(HttpSession session){
if(exists(session)){
...
}
}
#GetMapping("/addUser")
public ModelAndView user(HttpSession session){
if(exists(session)){
...
}
}
#GetMapping("/addBook")
public ModelAndView book(HttpSession session){
if(exists(session)){
...
}
}
Is there a way i can check session once the AdminController is called, instead of checking session for each mapping?
If you don't want to involve Spring Security, you can add an interceptor that will intercept all requests.
public class SessionCheck extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return exists(request.getSession());
}
}
First of all, in case you can, switch to Spring Boot. Configuration will be easier then.
Yes, in your WebSecurityConfigurerAdapterImpl you can specify, that the user must have a certain role to access certain areas. In your case it would be
http
.antMatches("/admin/**").hasRole(<UserRole.Admin>);
You can also use PreAuthorize:
#Controller
#RequestMapping("/admin")
public class AdminController{
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping
public ModelAndView admin(HttpSession session){
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping("/addUser")
public ModelAndView user(HttpSession session){
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping("/addBook")
public ModelAndView book(HttpSession session){
}
}
You have to enable using this annotation. It's described in the Documentation.
So summarizing, you need Spring Security, then either configure access in WebSecurityConfigurerAdapter or enable annotation based access control and then specify the #PreAuthorize.
Edit:
OK, as the OP has asked for clarification, I decided to add some more general information about using Spring Security.
In Spring Security we base on UserDetails and UserDetailsService which returns UserDetails on login. Although, I recommend you to refactor your code and just have one class User which has a Collection of authorities you can do the following.
Create new class which would be UserDetailsImpl which would implement UserDetails. Also create some custom roles like ROLE_USER, ROLE_ADMIN. They should be an enum. Then in your UserDetailsImpl have fields that are important during the session - logged user UUID, name, email and some somehow required by the interface - isAccountLocked, isPasswordExpired etc, Also you'll need a collection of authorities which you'll return by a certain method.
You'll be able to use hasRole in your code then. What is cool, you'll also be able to get the logged user details in your application from the context.
Related
I'm using Spring MVC 4 and I'm building a site with a template that requires several common components across pages, such as login status, cart status, etc. An example of controller function would be this:
#RequestMapping( path = {"/"}, method=RequestMethod.GET)
public ModelAndView index() {
ModelAndView mav = new ModelAndView("index");
mav.addObject("listProducts", products );
mav.addObject("listCategories", menuCategoriasUtils.obtainCategories());
return mav;
}
What would be a good way/pattern to feed these elements that do not belong to the controller we are currently calling so we don't repeat over and over unrelated operations in every method of every controller?
Thanks!
There is a several approaches to show common data in views. One of them is using of #ModelAttributte annotation.
Lets say, you have user login, that needed to be shown on every page. Also, you have security service, where from you will get security information about current login. You have to create parent class for all controllers, that will add common information.
public class CommonController{
#Autowired
private SecurityService securityService;
#ModelAttribute
public void addSecurityAttributes(Model model){
User user = securityService.getCurrentUser();
model.addAttribute("currentLogin", user.getLogin());
//... add other attributes you need to show
}
}
Note, that you don't need to mark CommonController with #Controller annotation. Because you'll never use it as controller directly. Other controllers have to be inherited from CommonController:
#Controller
public class ProductController extends CommonController{
//... controller methods
}
Now you should do nothing to add currentLogin to model attributes. It will be added to every model automatically. And you can access user login in views:
...
<body>
<span>Current login: ${currentLogin}</span>
</body>
More details about usage of #ModelAttribute annotation you can find here in documentation.
I am studying Spring Security and I have the following doubts related the difference between the use of the #Secured annotation and the #RolesAllowed annotation.
I know that both have to been used at method level, on my study material I found the followings 2 examples:
#RolesAllowed annotation:
import javax.annotation.security.RolesAllowed;
public class ItemManager {
#RolesAllowed("ROLE_MEMBER")
public Item findItem(long itemNumber) {
...
}
}
#Secured annotation:
import org.springframework.security.annotation.Secured;
public class ItemManager {
#Secured("ROLE_MEMBER")
public Item findItem(long itemNumber) {
...
}
}
It seems to me that these 2 annotations works in the same way. What are the differences? What am I missing?
Another doubt that I have is: what exactly represent the ROLE_MEMBER?
I think that this is something like role based security, so it could mean something like: only if the user is a member it could access to the annoted resource (is it correct?). But where and how is definied the fact that the user have setted this role (it is a member)? How exactly works?
Tnx
#Secured and #RolesAllowed are the same. They do the same operation in Spring.
But
#RolesAllowed - Standard annotation of Java.
Java has defined Java Specification Request, basically change requests for the Java language, libraries and other components. For the development of annotations, they have provided JSR 250. #RolesAllowed is included in it. This link contains further info in JSR 250
#Secured - Spring security annotation
ROLE_MEMBER is the role which is set to the security user details.
Refer this example from my current project. Here I'm using the user data object and mapping the roles given to the user to the security user details.
public class CustomUserDetails implements UserDetails {
...
...
...
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (Role role : this.user.getRoles()){
grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole()));
}
return grantedAuthorities;
}
}
These roles are then set for the security approvals using the #Secured or #RolesAllowed or #PreAuthorize("hasRole('ROLE_USER')") for the methods.
By design it's good to put the security in the Service layer. So when I'm securing my service actions, I check for the roles, not for the users.
This way, we can focus on the business logic and the security for the business logic via small security units called roles.
Then I assign the roles to the user. Users can have multiple roles. So you have to see the relationship here. Users are given the roles. And roles are given the access to the business logic. Users are given the access to the business logic via the roles. This concept is called, Role Based Access Control.
And in complex situations we can also manage hierarchical roles. Where one role has many other roles. But in the UserDetails, we have to flatten the role hierarchy and provide the list of roles to the Spring framework to process.
The accepted answer completely answers the question (heh), but I think this is a good place to say how to enable method level security in Spring.
The only thing You need to add is the #EnableGlobalMethodSecurity annotation on a configuration class (see the example) with the following properties set to true (default is false)
securedEnabled (enables Spring's Secured annotation.),
jsr250Enabled (enables the JSR-250 standard java security annotations, like RolesAllowed),
prePostEnabled (enables Spring's PreAuthorize and PostAuthorize annotations).
Example of annotation usage:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin(); // You probably need more than this
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// your authentication manager config here
}
For more detailed example, see Spring Security Method Level Annotations Example.
I’m trying to develop a Spring MVC application, now I encounter a question. When login successful I add the User entity to session and call http://localhost:8080/user to get the session user. Everything is OK here. But if I call the URL like this http://localhost:8080/user?username=testuser then the session user's username will change to testuser. What should I do that just get current user from session?
The code likes below
Entity:
#Entity
public class User {
private Long id;
private String username;
// ...Getter and Setter...
}
Controller:
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController {
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(#ModelAttribute("current_user") User user) {
return user;
}
}
Response of http://localhost:8080/user
[{"id":1,"username":"aaa111"}]
Response of http://localhost:8080/user?username=testuser; it should be same as above, but is
[{"id":1,"username":"testuser"}]
The #SessionAttributes annotation isn't intended for this. Its intend is to store objects in the session during http requests. Imagine a lengthy database call to retrieve an object you don't want to retrieve this object each time but probably reuse an existing one. The object is to be intended to be used as a #ModelAttribute, this annotation indicates that you want to use this object for binding (i.e. you have a form to change attributes of the object). When you are finished with the editing of the object you should make this clear by calling setComplete() on the SessionStatus object. See also here.
You want to store an object in the session and retrieve it when you need it. For this use the HttpSession in the normal way of calling setAttribute and getAttribute. To obtain the current HttpSession you can simply add a method argument of the type HttpSession and it will be injected for you. (See here for a list of supported method arguments).
public void myRequestHandlingMethod(HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
}
Or as you are already using Spring you could use the WebUtils for convenience. You can use the getSessionAttribute or getRequiredSessionAttribute methods to obtain the value from the session.
public void myRequestHandlingMethod(HttpServletRequest request) {
User currentUser = (User) WebUtils.getSessionAttribute("currentUser", request)
}
Another solution would be to extend Spring MVC. Spring MVC uses a HandlerMethodArgumentResolver to handle all the different types of method arguments. This mechanism is pluggable. You could create an annotation #CurrentUser and create a CurrentUserHandlerMethodArgumentResolver that will retrieve the user from the session and injects it in that place. You could then simply add your current user to your method signature.
public void myRequestHandlingMethod(#CurrentUser User user) { ... }
Configure the custom argument resolver
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.yourcomponany.app.web.CurrentUserHandlerMethodArgumentResolver />
</mvc:argument-resolvers>
</mvc:annotation-driven>
It also looks like you are rolling your own security framework, which I would advice against. Instead I would suggest using Spring Security instead. Advantage of this is that this provides integration with the Servlet API allowing for retrieval of the current Principal by either doing it yourself (request.getUserPrincipal()) or simply adding a method argument of the type java.security.Principal. It also comes with a custom HandlerMethodArgumentResolver which allows you to obtain the current Spring Security Authentication object.
try to get session value in controller from servlet request like below
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController{
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(HttpServletRequest request){
//false means do not create new session
HttpSession session = request.getSession(false);
return session != null?session.getAttribute("current_user"):null;
}
}
I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.
Am #SessionAttributes for maintaining SpringMVC.
Say, #SessionAttribute("user")
Currently am passing the object as ModelAttribute in all the controller, which needs to use the SessionObject "user" like
Class controller{
public ModelAndView method1(#ModelAttribute("user")){ }
public ModelAndView method2(#ModelAttribute("user")){ }
public ModelAndView method3(#ModelAttribute("user")){ }
public ModelAndView method4(#ModelAttribute("user")){ }
}
Is this the only way??
or
Is there any other way? such that I can have a base controller, which can return the session object by just extending the the base controller.
What I've been using in some of my projects is this:
String user = (String) hsr.getSession().getAttribute("user");
If you're looking for some sort of authentication I suggest you start using spring security or other authentication mechanisms that can filter out pages according to roles or authentication status.
Not sure what your exact requirement is, but what about creating a filter/interceptor that reads the value from session and stores it in a ThreadLocal that can be accessed by controllers later
The controllers that need to access #SessionAttributes you need to add the annotation as shown below.
#Controller
#SessionAttributes({"user"})
public class Controller {
.............
}
HTH