How to log SavedRequestAwareAuthenticationSuccessHandler.onAuthenticationSuccess with Spring Aspect? - spring

I try to log when user is successfully logged with Spring Security. I use Logging Aspect :
#Aspect
#Component
public class LoggingAspect {
static Logger log = Logger.getLogger(LoggingAspect.class);
#Before("execution(* com.jle.athleges.web.controller.MemberController.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("INFO - logBefore() is running!");
log.info(joinPoint.getSignature().getName());
}
#AfterReturning(pointcut = "execution(* org.springframework.security.authentication.AuthenticationManager.authenticate(..))", returning = "result")
public void after(JoinPoint joinPoint, Object result) throws Throwable {
log.info(">>> user: " + ((Authentication) result).getName());
}
#Around("execution(* org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler.onAuthenticationSuccess(..))")
public void onAuthenticationSuccess(){
log.info(">>> user " + (SecurityContextHolder.getContext().getAuthentication().getName()) + " is now connected");
}
}
Method after is running fine but log twice. I try with onAuthenticationSuccess but nothing is writed in console.
I use sample explained in Capture successful login with AspectJ and Spring Security but it is not working.
Any idea ?
Thanks

I found the solution !
I created a new SuccessHandler bean :
public class SecurityAuthenticationSuccessHandler extends
SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
}
}
And second point is to add it as a bean in the config and set it in formLogin :
#Bean
public SecurityAuthenticationSuccessHandler getSuccessHandler(){
return new SecurityAuthenticationSuccessHandler();
}
http.authorizeRequests().antMatchers("/*").permitAll().and()
.formLogin()
.successHandler(successHandler)
.permitAll().and().logout().permitAll();

Related

I can't update my webapp to Spring Boot 2.6.0 (2.5.7 works but 2.6.0 doesn't)

As mentioned in the title I can't update my webapp to Spring Boot 2.6.0. I wrote my webapp using Spring Boot 2.5.5 and everything works perfectly. If I update the pom.xml file with this new tag:
<version>2.5.7</version>
My webapp works perfectly. All tests work.
If I perform this update the webapp does not start:
<version>2.6.0</version>
Starting the DEBUG mode the IDE shows me an error and 2 links to 2 classes of my webapp.
2021-11-23 00:31:45.419 ERROR 21884 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'configurazioneSpringSecurity': Requested bean is currently in creation: Is there an unresolvable circular reference?
It seems the problem is in this class:
#Configuration
#EnableWebSecurity
public class ConfigurazioneSpringSecurity extends WebSecurityConfigurerAdapter {
#Autowired
LivelliDeiRuoli livelliDeiRuoli;
#Autowired
GestioneUtentiSpringSecurity gestioneUtentiSpringSecurity;
#Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
#Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers(
"/",
"/login",
"/benvenuto",
"/registrazione",
"/registrazione-eseguita",
"/pagine-applicazione"
).permitAll();
http.authorizeRequests().antMatchers("/area-riservata")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");
http.authorizeRequests().antMatchers("/cambio-password")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");
http.authorizeRequests().antMatchers("/cambio-nome")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(1L) + "')");
http.authorizeRequests().antMatchers("/cancella-utente")
.access("isAuthenticated()");
http.authorizeRequests().antMatchers("/gestione-utenti")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(2L) + "')");
http.authorizeRequests().antMatchers("/gestione-ruoli")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(3L) + "')");
http.authorizeRequests().antMatchers("/pannello-di-controllo")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(3L) + "')");
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/errore-403");
http.authorizeRequests().and().formLogin()
.loginProcessingUrl("/pagina-login")
.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?errore=true")
.usernameParameter("username")
.passwordParameter("password")
.and().logout().logoutUrl("/pagina-logout")
.logoutSuccessUrl("/login?logout=true");
http.authorizeRequests().and() //
.rememberMe().tokenRepository(this.persistentTokenRepository()) //
.tokenValiditySeconds(365 * 24 * 60 * 60);
http.authorizeRequests().antMatchers("/gestione-eventi")
.access("hasAnyRole('" + livelliDeiRuoli.elencoRuoli(2L) + "')");
http.authorizeRequests().antMatchers(
"/cerca-eventi",
"/ultimi-eventi"
).permitAll();
}
#Autowired
private DataSource dataSource;
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
or in this:
#SpringBootApplication
#Profile("sviluppo")
public class GestioneUtentiApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(GestioneUtentiApplication.class);
}
public static void main(String[] args) {
System.setProperty("server.servlet.context-path", "/gestioneutenti");
SpringApplication.run(GestioneUtentiApplication.class, args);
}
}
What's wrong with these classes?
What changes with Spring Boot 2.6.0?
GestioneUtentiSpringSecurity implements UserDetailsService:
#Service
public class GestioneUtentiSpringSecurity implements UserDetailsService {
#Autowired
private UtenteRepository utenteRepository;
#Autowired
private RuoloRepository ruoloRepository;
#Autowired
EseguiVariabiliDiSistema eseguiVariabiliDiSistema;
#Autowired
LivelliDeiRuoli livelliDeiRuoli;
#Override
public UserDetails loadUserByUsername(String nomeUtente) throws UsernameNotFoundException {
Utente utente = trovaUtenteConPrivilegiDiAutenticazione(nomeUtente);
if (utente == null) {
throw new UsernameNotFoundException("L'utente " + nomeUtente + " non รจ stato trovato nel database.");
}
List<String> ruoliUtente = null;
try {
ruoliUtente = this.ruoloRepository.trovaRuoliUtente(utente.getId());
}catch (Exception b){
ruoliUtente = null;
}
List<GrantedAuthority> grantList = null;
try{
grantList = new ArrayList<GrantedAuthority>();
if (ruoliUtente != null) {
for (String ruolo : ruoliUtente) {
GrantedAuthority authority = new SimpleGrantedAuthority(ruolo);
grantList.add(authority);
}
}
}catch (Exception c){
grantList = null;
}
UserDetails userDetails = null;
if((utente != null) && (ruoliUtente != null) && (grantList != null)){
userDetails = (UserDetails) new User(utente.getNome(), utente.getPassword(), grantList);
}
return userDetails;
}
public Utente trovaUtenteConPrivilegiDiAutenticazione(String nomeUtente){
try{
Utente utente = utenteRepository.trovaUtente(nomeUtente);
if(livelliDeiRuoli.requisitiUtenteConRuoloMassimo(utente)){
return utente;
} else{
eseguiVariabiliDiSistema.trovaVariabileSenzaVerificaUtente(
new VariabileSistema(0L, "login", "")
);
if(eseguiVariabiliDiSistema.getVariabileDiSistema().getValore().equals("true")){
return utente;
}else if(eseguiVariabiliDiSistema.getVariabileDiSistema().getValore().equals("false")){
return null;
}else{
return null;
}
}
}catch (Exception e){
return null;
}
}
}
Starting on Spring Boot 2.6, circular dependencies are prohibited by default. you can allow circular references again by setting the following property:
spring.main.allow-circular-references = true
You can read some more details about this in the Spring Boot 2.6 Release Notes.
The problem that Spring faces here and causes to not able to move forward starting from spring boot 2.6 with the default configuration of spring.main.allow-circular-references = false is located in the following part
#Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
#Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
I believe this is happening because the WebSecurityConfig extends WebSecurityConfigurerAdapter has some circular references in combination with BCryptPasswordEncoder inside this class.
The solution is to create another configuration class, where you can split the configurations so that spring is able to correctly create the beans avoiding circular references.
So you can create the following extra class
#Configuration
public class CustomSecurityConfig {
#Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
}
Then in your original ConfigurazioneSpringSecurity.class you can replace the failing
#Bean
public BCryptPasswordEncoder metodoCrittografia() {
return new BCryptPasswordEncoder();
}
#Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity).passwordEncoder(metodoCrittografia());
}
with the
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void crittografiaPassword(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(gestioneUtentiSpringSecurity)
.passwordEncoder(passwordEncoder);
}
Although setting the application.properties works, it is likely using a feature that is going to be deprecated. I was able to work around this by using setter based injection. It's a bit more verbose but might be a good starting point for those looking to stay current and not use features that might be deprecated down the line.
It's certainly an answer that can be improved upon and I hope others can contribute perhaps more concise answers. I'll update this if I find anything cleaner.
Before
#Component
public class CustomFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private JWTUtils jwtUtils;
//When any api will be called this method will be called first and this will extract
// Token from header pass to JWT Util calls for token details extraction
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
//implementation
}
}
After
#Component
public class CustomFilter extends OncePerRequestFilter {
private MyUserDetailsService myUserDetailsService;
public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) {
this.myUserDetailsService = myUserDetailsService;
}
public void setJwtUtils(JWTUtils jwtUtils) {
this.jwtUtils = jwtUtils;
}
private JWTUtils jwtUtils;
//When any api will be called this method will be called first and this will extract
// Token from header pass to JWT Util calls for token details extraction
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
//implementation
}
}
reference:
https://theintuitiveprogrammer.com/post-eight.html
I've this problem during migrate to spring boot 2.6.x with WebSecurityConfig code:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService userDetailsService() {
return email -> {
....
};
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
...;
}
fix according WebSecurityConfigurerAdapter#userDetailsServiceBean javadoc:
Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean ...
To change the instance returned, developers should change userDetailsService() instead
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Override
public UserDetailsService userDetailsService() {
return email -> {
....
};
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
...;
}

Spring Boot Mock MVC applying filter to wrong url pattern

I'm adding an admin filter to a specific URL like this
#Bean
public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean() {
FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>();
AdminFilter adminFilter = new AdminFilter();
registrationBean.setFilter(adminFilter);
registrationBean.addUrlPatterns("/api/user/activate");
registrationBean.addUrlPatterns("/api/user/deactivate");
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
While I'm testing it with postman or in browser, the filter is applied correctly, only applied to those URL pattern.
But, when I write test for it, somehow the filter is applied to another URL too.
this.mockMvc.perform(
get("/api/issue/").header("Authorization", defaultToken)
).andDo(print()).andExpect(status().isOk())
.andExpect(content().json("{}"));
This code return an error with code "403", on the log it says because the user is not an admin, which means the admin filter applied to "/api/issue/" URL on the mock mvc request.
I'm using #AutoConfigureMockMvc with #Autowired to instantiate the mockMVC.
anyone know why it's happening?
Full code of the admin filter:
#Component
public class AdminFilter extends GenericFilterBean {
UserService userService;
#Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
) throws IOException, ServletException {
if (userService == null){
ServletContext servletContext = servletRequest.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
userService = webApplicationContext.getBean(UserService.class);
}
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
User user = userService.fetchUserById(userId);
if (!user.getIsAdmin()) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
Full code of the test file:
#SpringBootTest()
#AutoConfigureMockMvc
#Transactional
public class RepositoryIntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private RepositoryRepository repositoryRepository;
#Autowired
private UserRepository userRepository;
private String defaultToken;
private String otherToken;
#BeforeEach
void init() {
User defaultUser = userRepository.save(new User("username", "email#mail.com", "password"));
System.out.println(defaultUser);
User otherUser = userRepository.save(new User("other", "other#mail.com", "password"));
defaultToken = "Bearer " + generateJWTToken(defaultUser);
otherToken = "Bearer " + generateJWTToken(otherUser);
}
private String generateJWTToken(User user) {
long timestamp = System.currentTimeMillis();
return Jwts.builder().signWith(SignatureAlgorithm.HS256, Constants.API_SECRET_KEY)
.setIssuedAt(new Date(timestamp))
.setExpiration(new Date(timestamp + Constants.TOKEN_VALIDITY))
.claim("userId", user.getId())
.compact();
}
#Test
public void shouldReturnAllRepositoriesAvailableToUser() throws Exception {
this.mockMvc.perform(
get("/api/issue/").header("Authorization", defaultToken)
).andExpect(status().isOk())
.andExpect(content().json("{}"));
}
}
Your AdminFilter is being registered twice. Once through the FilterRegistrationBean and once due to the fact that it is an #Component and thus detected by component scanning.
To fix do one of 2 things
Remove #Component
Re-use the automatically created instance for the FilterRegistrationBean.
Removing #Component is easy enough, just remove it from the class.
For option 2 you can inject the automatically configured filter into the FilterRegistrationBean configuration method, instead of creating it yourself.
#Bean
public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean(AdminFilter adminFilter) {
FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>(adminFilter);
registrationBean.addUrlPatterns("/api/user/activate");
registrationBean.addUrlPatterns("/api/user/deactivate");
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
An added advantage of this is that you can use autowiring to set up dependencies instead of doing lookups in the init method. I would also suggest using the OncePerRequestFilter. This would clean up your filter considerably.
#Component
public class AdminFilter extends OncePerRequestFilter {
private final UserService userService;
public AdminFilter(UserService userService) {
this.userService=userService;
}
#Override
protected void doFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws IOException, ServletException {
UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
User user = userService.fetchUserById(userId);
if (!user.getIsAdmin()) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}

How can i use multiple interceptors for specific request such as POST, Get and PUT?

I am using spring boot and cloud in the project. For logging, I am using Interceptors. Since I am new to interceptors I am having difficulty using multiple interceptors. Like can I use a specific interceptor for the specific task? For example, when I request a post, the POST interceptor is called, when I use GET the get interceptor is called. and how can I code for multiple interceptors too?
I never tried anything yet for that because I am not getting the logic
You can define all HTTP interceptors that you want, every interceptor should implement the logic of intercept an HTTP request.
#Slf4j
#Component
public class GetRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals(HttpMethod.GET.name())) {
log.info("intercepting GET request {}", request.getRequestURI());
}
return true;
}
}
#Slf4j
#Component
public class PostRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals(HttpMethod.POST.name())) {
log.info("intercepting POST request {}", request.getRequestURI());
}
return true;
}
}
And then you have to register them in spring.
#RequiredArgsConstructor
#Configuration
public class WebConfigurer implements WebMvcConfigurer {
private final GetRequestInterceptor getInterceptor;
private final PostRequestInterceptor postRequestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getInterceptor);
registry.addInterceptor(postRequestInterceptor);
}
}
#earandap
Your comment worked Thanks a lot.
Here is my code:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
log.info("[START] [" + request.getMethod() + "] [ URL is: " + request.getRequestURL().toString()
+ " Body is: {}]");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
long startTime = (long) request.getAttribute("startTime");
request.removeAttribute("startTime");
long endTime = System.currentTimeMillis();
log.info("[END] [" + request.getMethod() + "] [ URL is:" + request.getRequestURL().toString()
+ "] [Execution Time: {} miliseconds]", (endTime - startTime));
}

Spring Swagger 2 not working when i have a custom HandlerInterceptorAdapter

Im using a spring boot 1.5.3 along side swagger 2.7.0 (can't upgrade because higher versions dont work on IE)
i got everything to work fine but when i add a custom HandlerInterceptorAdapter its not working giving me this error:
Cannot read property 'validatorUrl' of null springfox.js: 72
here is my swagger config
#Configuration
#EnableSwagger2
public class SwaggerConfig {
public Docket api() {
Docket docket = new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors
.basePackage("net.guides.springboot2.springboot2swagger2.controller"))
.paths(PathSelectors.regex("/.*"))
.build().apiInfo(apiEndPointsInfo());
docket.ignoredParameterTypes(HttpServletResponse.class, HttpServletRequest.class); // this didnt help
return docket;
}
private ApiInfo apiEndPointsInfo() {
return new ApiInfoBuilder().title("SWAT Rest API")
.description("SWAT Rest API documentation")
.contact(new Contact("xxx", "xxxx", "xxxxx"))
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("5.2.2")
.build();
}
}
and here is the config that gives me a hard time
#Configuration
#Slf4j
public class SwatStaticResourceConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.debug("Adding static folders to serve images");
// registry.addResourceHandler("/images/**").addResourceLocations("file:./images/");
//registry.addResourceHandler("/docs/**").addResourceLocations("file:./docs/");
registry.addResourceHandler("/images/**", "/docs/**").addResourceLocations("file:./images/", "file:./docs/");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SwatRequestInterceptor()); // if i disable this line than swagger is working fine
super.addInterceptors(registry);
}
}
in case its needed here is the interceptor class
#Slf4j
public class SwatRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Principal user = request.getUserPrincipal();
if (user == null) {
log.error("No user for request: " + request.getRequestURL().toString());
return false;
} else {
log.debug("Got the following request: " + request.getMethod().toUpperCase() + " " + request.getRequestURL().toString() + ", FROM: " + user.getName());
return true;
}
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
Principal user = request.getUserPrincipal();
log.debug("Completed the following request: " + request.getMethod().toUpperCase() + " " + request.getRequestURL().toString() + ", FROM: " + user.getName());
super.postHandle(request, response, handler, modelAndView);
}
}
I was getting same issue but Resolved it by making sure that WebMvcConfigurationSupport is extended only by a single class preferably where you are registering your interceptor.
Also, make sure to add the following for swagger config class:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

Creating Custom AuthenticationSuccessHandler and calling default AuthenticationSuccessHandler when done

i want to create CustomAuthenticationSuccessHandler to add log to each user login:
class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
UserService getUserService() {
return ApplicationContextHolder.getBean('userService')
}
#Override
public void onAuthenticationSuccess(HttpServletRequest req,
HttpServletResponse res, Authentication auth) throws IOException,
ServletException {
userService.postLoginMessage(auth.principal.username, null)
}
}
it's simple enough code and works well, but now i have no return value. is there a way to call the default AuthenticationSuccessHandler after my custom code run or to return default value?
I mean to return the Authentication json.
You're doing it wrong essentially.
Instead of overriding the success handler, you should register an event listener.
Config:
grails.plugin.springsecurity.useSecurityEventListener = true
Register a class as a bean
class MySecurityEventListener
implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Autowired
UserService userService
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
userService.postLoginMessage(event.authentication.principal.username, null)
}
}

Resources