Ever wondered who is currently logged in to your app or how to kick them out? . You can do it using SessionRegistry.
(Disclaimer: UI is self-coded, not provided by Spring)
First, setup spring security configuration
Java config:
.and()
.sessionManagement()
.maximumSessions(1) // How many session the same user can have? This can be any number you pick
.expiredUrl("/login?expired")
.sessionRegistry(sessionRegistry)
And register the sessionRegistry bean:
(name = "sessionRegistry")
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
Or XML config, place this below :
Now you can list currently active sessions
Inject a SessionRegistry and let’s see who’s currently logged in:
private SessionRegistry sessionRegistry;
public List getActiveSessions() {
List activeSessions = new ArrayList<>();
for(Object principal : sessionRegistry.getAllPrincipals()) {
activeSessions.addAll(sessionRegistry.getAllSessions(principal, false));
}
return activeSessions;
}
Principal is just a fancy word for user in security speak. Note how SessionInformation returns a principal of type Object. If you use Spring Security your principal will most likely be the type of org.springframework.security.core.userdetails.User
Here’s how you can get the username from a User object:
SessionInformation session = ... // get the session info somehow
Object principalObj = session.getPrincipal();
if (principalObj instanceof User) {
User user = (User) principalObj;
return user.getUsername();
}
If you need to kick someone out
The use case of this is not just for when you hated a particular user so much, but if user permission is updated, then you have to invalidate all active sessions for it to take effect.
public void logoutSession(String sessionId) {
SessionInformation session = sessionRegistry.getSessionInformation(sessionId);
if (session != null) {
session.expireNow();
}
}
This setup only works assuming you have on your main configuration class. By default Spring Security will provide a login form at /login but you can implement your own. I’ve also setup two users to test it
Spring Security comes handy when you need to secure your RESTful web service. Let’s give this a go! In this example I’ll create a REST service exposing a Cat entity. It simply have name and colour field
{
"name" : "Tom",
"colour" : "Black"
};
Creating the Service
Setup a new maven project with following pom.xml. We’ll leverage Spring Boot to simplify the work.
Setup a main configuration class. This substitutes the old-fashioned spring context xml. Spring Boot will do a lot of under-the-hood work to setup various bits and pieces using auto configuration
guration
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Now let’s create our Cat JPA entity
public class Cat {
@Id
private long id;
private String name = "";
private String colour = "";
/* .. getters & setters omitted .. */
}
Since we included dependency to spring-boot-starter-data-jpa, Spring Boot will automatically set us up with JPA with Hibernate implementation. Also note that on pom.xml we declared a dependency to hsqldb which will automatically give us datasource to an embedded HSQL database.
Next, let’s create a Spring Data repository for Cat.
esource
public interface CatRepository extends PagingAndSortingRepository {
}
Again, spring-boot-starter-data-jpa will auto setup spring-data-jpa for me, and it will automatically provide an implementation of the repository interface at runtime.
Take a note at the esource annotation. This annotation tells Spring Data REST to expose the repository as REST service as well.
Testing the Service
Now you’re ready to run the app using the embedded tomcat container. Run following maven command
Next, configure Spring Security so /cats/** path are protected to users with ROLE_USER only. We also setup two in-memory users: bob with ROLE_USER and admin with ROLE_USER and ROLE_ADMIN
Don’t forget to recompile and restart the app. Now you can test querying the cats repository will be forbidden for non-authenticated (anonymous) users:
The -H option passes a HTTP header as part of the request. Also note that the username and password joined by colon (:) is encoded into Base64 resulting in the string Ym9iOmJvYjEyMw==. This is the string “bob:bob123″ in plain text. You can use an to try it yourself.
Also note that similar to web browser the server gave us a JSESSIONID Cookie:
Different Security Permission for Read / Update Operations
What if you want to give ROLE_USER read-only access and full read/write to ROLE_ADMIN?
This can be achieved by using Spring Data Repository event handler class. You can invoke custom code prior / after certain operations executed on the repository. Let’s see how this works.
First enable method security on our SecurityConfig class. Add the hodSecurity(securedEnabled = true) annotation to SecurityConfig class:
ty
hodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Create a new CatEventHandler class annotated with Handler(Cat.class)
Handler(Cat.class)
("ROLE_ADMIN")
public class CatEventHandler {
private static final Logger LOG = LoggerFactory.getLogger(CatEventHandler.class);
e
public void handleBeforeSave(Cat c) {
LOG.debug("Before save " + c);
}
ate
public void handleBeforeCreate(Cat c) {
LOG.debug("Before create " + c);
}
kSave
public void handleBeforeLinkSave(Cat c) {
LOG.debug("Before link save " + c);
}
ete
public void handleBeforeDelete(Cat c) {
LOG.debug("Before delete " + c);
}
kDelete
public void handleBeforeLinkDelete(Cat c) {
LOG.debug("Before link delete " + c);
}
}
Few important things happening here:
All the .. methods will be invoked before the corresponding response is given to users
The (“ROLE_ADMIN”) annotation will ensure only users with ROLE_ADMIN can invoke all those methods in the class (although the method has nothing in it except a logging statement)
Let’s give this a try. Authenticate as bob:bob123 again and try creating a new Cat. Error will be presented:
I find it annoying to keep having to encode our username and password into Base64. I wanted a more simplified login such as posting u=bob&p=bob123 to http://localhost:8080/login.
Let’s try doing this.
Borrowing idea from , let’s create a LoginController
("/login")
public class LoginController {
private SecurityConfig securityConfig;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private static final Logger LOG = LoggerFactory.getLogger(LoginController.class);
(method = RequestMethod.POST)
public String login(("u") String username,
("p") String password,
HttpServletRequest req) throws Exception {
// Force session creation so it's available to Spring Security post processor filter
req.getSession(true);
// Authenticate using AuthenticationManager configured on SecurityContext
AuthenticationManager authMgr = securityConfig.authenticationManagerBean();
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
authReq.setDetails(authenticationDetailsSource.buildDetails(req));
Authentication authResp = authMgr.authenticate(authReq);
// If successful add the authentication response to context so the post processor filter
// can save it to session
SecurityContextHolder.getContext().setAuthentication(authResp);
return "Authentication successful";
}
...
}
Also create some exception handlers so login failure will produce 401 – Unauthorized HTTP status code.
Yay! Well done on making it this far. Hope you get a pretty decent looking REST API with minimal effort thanks to Spring. Don’t forget to always serve your API in HTTPS if deploying in production environment and secure it further with firewall if applicable.
As always you can browse the source code of this article on github:
Here’s how you can use Spring Security LDAP just for password authentication, but use your own database for assigning role / authorities.
First on your context xml define an LDAP server context bean as per normal:
Then define LDAPAuthenticationProvider bean:
If you noticed at the bottom we set authoritiesPopulator into myLDAPAuthPopulator bean which we’ll define next. This is where you can lookup your database using jdbc or other method to populate the roles given an authenticated username:
("myLDAPAuthPopulator")
public class MyLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public Collection extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
List authorities = new ArrayList();
User user = userDAO.findByUsername(username);
for(String role : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
And finally register this authentication provider in the authentication manager:
You can also check if the user has enough right to access a resource. This example ensures user has enough rights to access /admin or else the link won’t be rendered:
Asking your user to login immediately after he/she registers might be tedious. Here’s how you can log them in immediately using Spring Security (thanks to this SO thread).
Typically you will have some sort of registration form with a backing controller like this:
("/register")
public class RegisterController {
...
(method = POST)
public String register( User user) {
// perform registration logic..
// redirect back to login page
return "redirect:/login";
}
...
}
("/register")
public class RegisterController {
...
("authMgr") private AuthenticationManager authMgr;
private UserDetailsService userDetailsSvc;
(method = POST)
public String register( User user) {
// perform registration logic..
// perform login authentication
try {
UserDetails userDetails = userDetailsSvc.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
authMgr.authenticate(auth);
// redirect into secured main page if authentication successful
if(auth.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(auth);
return "redirect:/";
}
} catch (Exception e) {
logger.debug("Problem authenticating user" + username, e);
}
return "redirect:/error";
}
...
}
Note that in above code the AuthenticationManager injection is qualified by ("authMgr"). This is to avoid multiple beans ambiguity. In effect in the xml context configuration (if you use one) an id attribute has to be set:
...
...
...
...
Also in order for this setup to work, the registration page has to be filtered by spring security
Add a security-context.xml spring beans config file. You can place all your security config on root application context xml but separating it would produce clearer code without namespace prefix clutter
Add basic HTTP form authentication and provider as seen on Spring reference manual
Gerry's software development journey of trial, errors and re-trials