I added spring security dependency, after that I started the server, everything goes well. As a result I get a form with Username and password, I enter user as username and as a password the one I get in the console,
Using generated security password: 8c3450f7-ab6c-419e-bd69-431ed336eeaa
But I always get username or password are incorrect.
Class Security:
package security;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/users")
.permitAll()
.anyRequest().authenticated();
}
}
Here is the link I'm trying to access http://localhost:8081/users
Any ideas about mistakes I'm making?
Class UserController:
RestController
#RequestMapping("/users") // localhosr:8080/users
public class userController {
#Autowired
UserService userService;
#GetMapping
public String getUser() {
return " get user was called ";
}
#PostMapping
public UserResponse createUser(#RequestBody UserRequest userRequest) {
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userRequest, userDto);
UserDto createUser = userService.createUser(userDto);
UserResponse userResponse = new UserResponse();
BeanUtils.copyProperties(createUser, userResponse);
return userResponse ;
}
#PutMapping
public String updateUser() {
return " update user was called ";
}
#DeleteMapping
public String deleteUser() {
return " delete user was called ";
}
}
In insomnia, for Get Method, I'm getting error 401 Unauthorized
PS: I've removed the class WebSecurity, and I'm trying to access localhost:8081/users, I'm always getting a login form and can't get logged in.
You will configuration to ALL end-point to required authenticated by "anyRequest().authenticated()". So, ofc you will get 401 all time.
First way
You can configuration all path requires one-by-one. Like:
.antMatchers("/to/path/**").isAuthenticated() If you required auth.
.antMatchers("/to/path/**").isAnonymous() If you required no-auth, so you will get 401 again with auth
.antMatchers("/to/path/**").permitAll() It does matter user was authenticated or not
.antMatchers("/to/path/**").hasRole(...) Refined regulation (in future)
Second way:
I recommend use it with: "anyRequest().permitAll()"
Use annotation on configuration class:
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
...
}
After it, you can control method level. Example:
#PreAuthorize("isAuthenticated()") // this isAuthenticated() see above first way
#PutMapping
public String updateUser() {
...
}
Those that do not have a security annotation are the default permitAll.
Link: https://www.baeldung.com/spring-security-method-security
Side note:
It also work on spring react (webflux), just replace EnableGlobalMethodSecurity(...) with: #EnableReactiveMethodSecurity. And of course, the setting is a bit different, but this can be searched online.
Related
I set-up my Springboot project with Spring Security and implemented the Spring Configuration with In-memory authentication. Just a simple authenticated. However, no matter what I do it's not working at all and is giving me 401 Unauthorized for any request in both Spring Security Login form and Postman.
I tried different solution but nothing seems to be working. I get 401 every single time.
I tried adding the componentScan, the filter order, the auto config exclusion, simple password, encrypted password. Nothing works.
Application:
#EnableEurekaClient
#SpringBootApplication
#ComponentScan(basePackages = "sdp.training.gatewayservice.security")
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
Security Configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// First encrypt the password string
String encodedPassword = passwordEncoder().encode("javainuse");
// Set the password
UserDetails user = User.builder()
.username("javainuse")
.password(encodedPassword)
.roles("USER")
.build();
// Use in-memory authentication with BCryptEncoder
auth.inMemoryAuthentication()// .passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(user)
.passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/testBruh").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Simple Controller
#RestController
#RequestMapping(value = "/testBruh")
public class TestController {
#GetMapping
public ResponseEntity<Object> getAccounts() {
return ResponseEntity.status(HttpStatus.OK).body("YO");
}
}
Application.properties
spring.application.name: gateway-service
server.port: 8090
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
# spring.security.user.name=admin
# spring.security.user.password=123456
# spring.security.user.roles=manager
spring.security.filter.order=10
spring.main.allow-bean-definition-overriding=true
spring.cloud.gateway.routes[0].id=accountService
spring.cloud.gateway.routes[0].uri=lb://account-service
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args.pattern=/account-service/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].id=blogService
spring.cloud.gateway.routes[1].uri=lb://blog-service
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args.pattern=/blog-service/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
I am currently working on a Spring Boot REST application with Spring Security. My workplace use Auth0 (external third-party service providing user management) for their authentication and have requested me to implement it in this application. Authentication occurs in the front end application written in React. The frontend application shows a login form and sends the username and password to Auth0, Auth0 verifies the credentials and returns a JWT token when the user is validated.
After this, the frontend application will call the REST services from my application passing a JWT token in the Authorize header. Using an Auth0 plugin, Spring Security verifies this token and the request is allowed to execute. I have tested this much to be working as expected. The code is as follows:
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.auth0.spring.security.api.JwtWebSecurityConfigurer;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Value(value = "${auth0.apiAudience}")
private String apiAudience;
#Value(value = "${auth0.issuer}")
private String issuer;
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
configuration.addAllowedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
JwtWebSecurityConfigurer //Auth0 provided class performs per-authentication using JWT token
.forRS256(apiAudience, issuer)
.configure(http)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/Test/public").permitAll()
.antMatchers(HttpMethod.GET, "/Test/authenticated").authenticated();
}
}
Now, once this authentication is done, I have observed that the principal in the security context gets updated with user id from Auth0. I have verified this by this code snippet:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName(); // Returns the Auth0 user id.
The next step I expect to do is to use this user id to match the user with roles and permissions in my existing database schema. Therefore, I need to implement a custom authorization mechanism that plugs into Spring Security as well. In other words the user's roles must be loaded into the security context shortly after the (pre)authentication is done. How do I implement this? Is there some class that I need to extend or implement some interface?
I think what you are looking for is the AuthenticationProvider Interface. Here are two examples how I handle Authentication:
DaoAuthentication
#Component
public class DaoAdminAuthenticationProvider extends DaoAuthenticationProvider {
private static final Logger LOG =
LoggerFactory.getLogger(DaoAdminAuthenticationProvider.class);
private final AdminUserRepository adminUserRepository;
public DaoAdminAuthenticationProvider(AdminUserRepository adminUserRepository, DaoAdminUserDetailsService daoAdminUserDetailsService) {
this.adminUserRepository = adminUserRepository;
setPasswordEncoder(new BCryptPasswordEncoder(11));
this.setUserDetailsService(daoAdminUserDetailsService);
}
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
AdminUser adminUser = adminUserRepository.findByEmail(auth.getName());
if (adminUser == null) {
LOG.info("Invalid username or password");
throw new BadCredentialsException("Invalid username or password");
}
Authentication result = super.authenticate(auth);
return new UsernamePasswordAuthenticationToken(adminUser, result.getCredentials(), result.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
JwtAuthenticationProvider
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOG =
LoggerFactory.getLogger(JwtAuthenticationProvider.class);
private static final String EX_TOKEN_INVALID = "jwt.token.invalid";
private final JwtTokenService jwtTokenService;
#SuppressWarnings("unused")
public JwtAuthenticationProvider() {
this(null);
}
#Autowired
public JwtAuthenticationProvider(JwtTokenService jwtTokenService) {
this.jwtTokenService = jwtTokenService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
String token = (String) authentication.getCredentials();
String username = jwtTokenService.getUsernameFromToken(token);
return jwtTokenService.validateToken(token)
.map(aBoolean -> new JwtAuthenticatedProfile(username))
.orElseThrow(() -> new TokenException(EX_TOKEN_INVALID));
} catch (JwtException ex) {
LOG.error("Invalid JWT Token");
throw new TokenException(EX_TOKEN_INVALID);
}
}
#Override
public boolean supports(Class<?> authentication) {
return JwtAuthentication.class.equals(authentication);
}
}
The other classes like JwtTokenService etc. I implemented as well. But regarding to your question I think the answer is to use the AuthenticationProvider Interface.
Ok, I found a solution though I think it's a bit dirty. Going by the weird way that the official Auth0 classes are structured, what I've done could possibly be described as a hack. Anyway, here goes:
First of all, I a custom user details service by implementing the AuthenticationUserDetailsService interface:
#Service
public class VUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationJsonWebToken> {
#Autowired
UserRepository userRepository;
Logger logger = LoggerFactory.getLogger(VUserDetailsService.class);
#Override
#Transactional(readOnly = true)
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationJsonWebToken token) throws UsernameNotFoundException {
logger.debug("User id: "+token.getName());
// Verify whether there is an entry for this id in the database.
User user = userRepository.findByAuxillaryId(token.getName());
if(user == null)
throw new UsernameNotFoundException("The user with id "+token.getName()+" not found in database.");
logger.debug("Obtained user details from db: "+user.toString());
List<GrantedAuthority> authoritiesList = new ArrayList<>();
// Get user roles
List<UserRole> userRoles = user.getUserRoles();
if(userRoles != null) logger.debug("Number of user roles:"+userRoles.size());
for(UserRole userRole : userRoles) {
logger.debug(userRole.getCompositeKey().getRole());
authoritiesList.add(new SimpleGrantedAuthority(userRole.getCompositeKey().getRole()));
}
return new org.springframework.security.core.userdetails.User(token.getName(), "TEMP", authoritiesList);
}
}
Here auxillary id is the user id assigned when a user is created in Auth0. Note that PreAuthenticatedAuthenticationJsonWebToken is a class provided by Auth0 as well.
After this, I created a custom authentication provider extending the Auth0 provided JwtAuthenticationProvider:
public class VAuthenticationProvider extends JwtAuthenticationProvider {
public VAuthenticationProvider(JwkProvider jwkProvider, String issuer, String audience) {
super(jwkProvider, issuer, audience);
}
#Autowired
VUserDetailsService vUserDetailsService;
Logger logger = LoggerFactory.getLogger(VAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("*** Processing authentication for token: "+authentication.getName());
logger.debug("*** Current granted authorities: "+authentication.getAuthorities());
UserDetails userDetails = vUserDetailsService.loadUserDetails((PreAuthenticatedAuthenticationJsonWebToken) authentication);
authentication = new PreAuthenticatedAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
return authentication;
}
#Override
public boolean supports(Class<?> authentication) {
//com.auth0.spring.security.api.authentication.PreAuthenticatedAuthenticationJsonWebToken
return authentication.equals(PreAuthenticatedAuthenticationJsonWebToken.class);
}
}
Then I used this authentication provider in my security configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value(value = "${auth0.apiAudience}")
private String apiAudience;
#Value(value = "${auth0.issuer}")
private String issuer;
#Autowired
VUserDetailsService vUserDetailsService;
Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
#Bean
public VAuthenticationProvider authProvider() {
JwkProvider jwkProvider = new JwkProviderBuilder(issuer).build(); //Auth0 provided class
VAuthenticationProvider vAuthProvider = new VAuthenticationProvider(jwkProvider, issuer, apiAudience);
return vAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
JwtWebSecurityConfigurer.forRS256(apiAudience, issuer, authProvider())
.configure(http)
.authorizeRequests().antMatchers(HttpMethod.GET, "/Test/public").permitAll()
.antMatchers(HttpMethod.GET, "/Test/authenticated").authenticated()
.antMatchers(HttpMethod.GET, "/admin/*").hasRole("ADMIN") //Not Auth0 role, defined in my DB.
.antMatchers(HttpMethod.GET, "/Test/root").hasRole("ROOT"); //Not Auth0 role, defined in my DB.
}
/* Code ommitted */
Now, all my requests are getting filtered based on the roles in my database. Thus, Auth0 is only being used for authentication and authorization is based on roles in my database.
If anyone thinks this solution could be improved, please let me know.
I precise that I am a french student in 1st year of Java Developper.
I'm developing a little multi-module app using: Spring Boot, Spring security, Hibernate, Spring Data, Spring MVC and Thymeleaf.
I would like to set the User in the session, or at least the userId, at login. This way I don't have to put it manually in the session or in the model each time I need it.
But as I use the default Spring Security login and authentication configuration, I really don't know how or where to call such a method:
void putUserInHttpSession( HttpSession httpSession ) {
httpSession.setAttribute( "user" , getManagerFactory().getUserManager().findByUserName( SecurityContextHolder.getContext().getAuthentication().getName()) );
}
I can do it eahc time I need it but I find it pretty ugly not to just do this at login in!
Here are what I think you might need to help me (that would be AWESOME !!! :)
My WebSecurityConfig class:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// Setting Service to find User in the database.
// And Setting PassswordEncoder
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.csrf().disable();
// /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
// If no login, it will redirect to /login page.
http.authorizeRequests().antMatchers(
"/user/**")
.access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");
// For ADMIN only.
http.authorizeRequests().antMatchers(
"/admin/**")
.access("hasRole('ROLE_ADMIN')");
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/public/login").defaultSuccessUrl("/public/showAtlas")//
.failureUrl("/public/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
//Config for Logout Page
.and()
.logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");
http.authorizeRequests().antMatchers(
"/public/**").permitAll();
// The pages does not require login
}
}
My UserDetailsServiceImpl class:
#Service
public class UserDetailsServiceImpl implements UserDetailsService{
#Autowired
private ManagerFactory managerFactory;
// private HttpSession httpSession;
/**
* The authentication method uses the user email, since it is easier to remember for most users
* #param input
* #return a UserDetails object
* #throws UsernameNotFoundException
*/
#Override
public UserDetails loadUserByUsername( String input) throws UsernameNotFoundException {
User user = new User();
if( input.contains( "#" )){
user = this.managerFactory.getUserManager().findByEmail( input );
}
else {
user = this.managerFactory.getUserManager().findByUserName( input );
}
if (user == null) {
throw new UsernameNotFoundException( "User with email " + input + " was not found in the database" );
}
// [ROLE_USER, ROLE_ADMIN,..]
List<String> roleNames = this.managerFactory.getRoleManager().findRoleByUserName(user.getUserName());
List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
if (roleNames != null) {
for (String role : roleNames) {
// ROLE_USER, ROLE_ADMIN,..
GrantedAuthority authority = new SimpleGrantedAuthority(role);
grantList.add(authority);
}
}
return (UserDetails) new org.springframework.security.core.userdetails.User(user.getUserName(),
user.getPassword(), grantList);
}
}
My simple LoginController:
#Controller
public class LoginController{
#GetMapping("/public/login")
public String login(Model model ){
return "view/login";
}
#GetMapping("/public/logoutSuccessful")
public String logout(Model model) {
return "view/logoutSuccessful";
}
So, is there a simple way to put the user or userId in the httpSession at login?
Thank you very much guys!!!
THE SOLUTION
Create a CustomAuthenticationSuccessHandler
#Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private ManagerFactory managerFactory;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
String userName = "";
HttpSession session = request.getSession();
Collection< GrantedAuthority > authorities = null;
if(authentication.getPrincipal() instanceof Principal ) {
userName = ((Principal)authentication.getPrincipal()).getName();
session.setAttribute("role", "none");
}else {
User userSpringSecu = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
session.setAttribute("role", String.valueOf( userSpringSecu.getAuthorities()));
session.setAttribute( "connectedUser" , managerFactory.getUserManager().findByUserName( userSpringSecu.getUsername() ) );
}
response.sendRedirect("/public/showAtlas" );
}
}
Then Autowired this class and add it in the WebSecurityConfigurerAdapter
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Autowired
private DataSource dataSource;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// Setting Service to find User in the database.
// And Setting PassswordEncoder
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.csrf().disable();
// /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
// If no login, it will redirect to /login page.
http.authorizeRequests().antMatchers(
"/user/**")
.access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");
// For ADMIN only.
http.authorizeRequests().antMatchers(
"/admin/**")
.access("hasRole('ROLE_ADMIN')");
// http.exceptionHandling().accessDeniedPage( "/error/403" );
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/public/login")
.defaultSuccessUrl("/public/showAtlas")//
.successHandler( customAuthenticationSuccessHandler )
.failureUrl("/public/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
//Config for Logout Page
.and()
.logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");
http.authorizeRequests().antMatchers(
"/public/**").permitAll();
// The pages does not require login
}
}
Assuming you wanted to add user to session on seccessful login, You can create the AuthenticationSuccessHandler like below and register using successHandler(new AuthenticationSuccessHandlerImpl())
Update:
If we create the object AuthenticationSuccessHandlerImpl, it will not be spring mananged and hence autowire into your Securityconfig and use it like shown below.
Here autowire the AuthenticationSuccessHandler in your WebSecurityConfig
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
and use it
WebSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll().successHandler(authenticationSuccessHandler) // See here
.and()
.logout()
.permitAll();
}
The AuthenticationSuccessHandlerImpl.java
import java.io.IOException;
import java.security.Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.techdisqus.auth.repository.UserRepository;
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler{
#Autowired HttpSession session; //autowiring session
#Autowired UserRepository repository; //autowire the user repo
private static final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandlerImpl.class);
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
String userName = "";
if(authentication.getPrincipal() instanceof Principal) {
userName = ((Principal)authentication.getPrincipal()).getName();
}else {
userName = ((User)authentication.getPrincipal()).getUsername();
}
logger.info("userName: " + userName);
//HttpSession session = request.getSession();
session.setAttribute("userId", userName);
}
}
Hope this helps.
Let me supplement above two solutions. My experience showed the following statement initiated below exception:
session.setAttribute("userId", userName);
Exception:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread?
And I was able to remove it after studying Using a request scoped bean outside of an actual web request. That is, I've overridden onStartup method in the class which extends AbstractAnnotationConfigDispatcherServletInitializer class.
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
Another approach: Registered a bean listening for Spring Security's InteractiveAuthenticationSuccessEvent and SessionDestroyedEvent events. These events fire without any explicit configuration in a Spring Boot app.
See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.security:
The basic features you get by default in a web application are:
. . .
A DefaultAuthenticationEventPublisher for publishing authentication events.
Handling these events, you can add username as a session attribute immediately after a user logons and remove that attribute when the security session (security context) is destroyed:
#Component
public class SessionStoreUsernameAuthEventHandler {
#EventListener
public void audit(InteractiveAuthenticationSuccessEvent e) {
getSession().ifPresent(s -> s.setAttribute("username", e.getAuthentication().getName()));
}
#EventListener
public void audit(SessionDestroyedEvent e) {
getSession().ifPresent(s -> s.removeAttribute("username"));
}
private static Optional<HttpServletRequest> getCurrentRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
private static Optional<HttpSession> getSession() {
return getCurrentRequest().map(HttpServletRequest::getSession);
}
}
//Part of my Controller class
#RequestMapping("/login")
public String login(HttpServletRequest request,HttpServletResponse response) {
request.setAttribute("mode", "MODE_LOGIN");
return "welcomepage";
}
#RequestMapping ("/login-user")
public String loginUser(#ModelAttribute User user, HttpServletRequest request,HttpServletResponse response) {
if((userService.findByUsernameAndPassword(user.getUsername(), user.getPassword())!=null)) {
Cookie loginCookie=new Cookie("mouni","user.getUsername()");
loginCookie.setMaxAge(30*5);
response.addCookie(loginCookie);
return "homepage";
}
else {
request.setAttribute("error", "Invalid Username or Password");
request.setAttribute("mode", "MODE_LOGIN");
return "welcomepage";
}
}
I am doing a library management project on java spring boot.
I have one problem, i would like to do authentication using cookies.
In brief, Once after user logged in with his credentials, username should be saved as cookie value. Next time when user is going to login, he can just enter username and should be logged in successfully.
Could someone please help me out
Since security is a complex matter, I recommend using Spring Security, even though you're tasked to do it without. To illustrate the complexity about security, I can already tell you that your current code has a vulnerability, since you're trusting a plaintext username cookie as your sole authentication. Spring Security on the other hand uses a key to generate a remember me cookie so that it is much more difficult to impersonate someone (unless you know the key).
So, if you would be using Spring Security, the first thing you need to do is to create a UserDetailsService, which has a method called loadByUsername(). To implement this, you could use your UserService and use the User builder to construct a Spring Security user object:
public class MyUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("admin".equalsIgnoreCase(username)) {
return User.builder()
.username(username)
// This should contain the hashed password for the requested user
.password("$2a$10$T5viXrOTIkraRe2mZPyZH.MAqKaR6x38L.rbmRp53yQ8R/cFrJkda")
// If you don't need roles, just provide a default one, eg. "USER"
.roles("USER", "ADMIN")
.build();
} else {
// Throw this exception if the user was not found
throw new UsernameNotFoundException("User not found");
}
}
Be aware, in contrary to your original UserService.findByUsernameAndPassword() you do not have to check the password by yourself, just retrieve the user object and pass the hashed password.
The next step is to provide a proper PasswordEncoder bean. In my example I'm using BCrypt with 10 rotations, so I created the following bean:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
public UserDetailsService userDetailsService() {
return new MyUserDetailsService();
}
}
The next step is to configure Spring Security. For example:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html").permitAll()
.loginProcessingUrl("/login-user").permitAll().usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/welcome.html")
.and()
.rememberMe()
.alwaysRemember(true)
.tokenValiditySeconds(30*5)
.rememberMeCookieName("mouni")
.key("somesecret")
.and()
.csrf().disable();
}
In this case, all endpoints (/**) will be secured, you'll have a login form at login.html containing two form fields (username and password). The destination of the form should be /login-user and when a user is successfully logged in, he will be redirected to /welcome.html.
Similar to what you wrote in your code, this will generate a cookie called mouni containing a value (no longer a plain username) and it will be valid for 150 seconds, just like in your example.
I'm disabling CSRF here because I'm using a simple HTML form, and otherwise I would have to add a templating engine to pass the CSRF key. Ideally, you should keep this enabled.
You are using Spring framework which has the capability for the same which you are trying to achieve. so why to do it manually?
Have a look at spring security.
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/
#RestController
public class AuthenticationController {
#RequestMapping("/")
protected Principal login(Principal user) {
ObjectMapper mapper = new ObjectMapper();
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
System.out.println(SecurityContextHolder.getContext().getAuthentication().getDetails());
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
System.out.println("testing testing xyz");
return user;
}
}
This is my code. I have tried with maximum possible ways to get details of the user. Actually i want email of the user but when I'm returning "user" -- principal object, it is giving json on the screen. Please help me on this..
Added spring security configuration... Please go through it and let me know if I made any thing wrong.. and my scope is openid, email, profile
package com.ggktech;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
/**
* Modifying or overriding the default spring boot security.
*/
#Configurable
#EnableWebSecurity
public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
private OAuth2ClientContext oauth2ClientContext;
private AuthorizationCodeResourceDetails authorizationCodeResourceDetails;
private ResourceServerProperties resourceServerProperties;
#Autowired
public void setOauth2ClientContext(OAuth2ClientContext oauth2ClientContext) {
this.oauth2ClientContext = oauth2ClientContext;
}
#Autowired
public void setAuthorizationCodeResourceDetails(AuthorizationCodeResourceDetails authorizationCodeResourceDetails) {
this.authorizationCodeResourceDetails = authorizationCodeResourceDetails;
}
#Autowired
public void setResourceServerProperties(ResourceServerProperties resourceServerProperties) {
this.resourceServerProperties = resourceServerProperties;
}
/* This method is for overriding the default AuthenticationManagerBuilder.
We can specify how the user details are kept in the application. It may
be in a database, LDAP or in memory.*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/* This method is for overriding some configuration of the WebSecurity
If you want to ignore some request or request patterns then you can
specify that inside this method.*/
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
/*This method is used for override HttpSecurity of the web Application.
We can specify our authorization criteria inside this method.*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// Starts authorizing configurations.
.authorizeRequests()
// Ignore the "/" and "/index.html"
.antMatchers("/", "/**.html", "/**.js").permitAll()
// Authenticate all remaining URLs.
.anyRequest().fullyAuthenticated()
.and()
// Setting the logout URL "/logout" - default logout URL.
.logout()
// After successful logout the application will redirect to "/" path.
.logoutSuccessUrl("/")
.permitAll()
.and()
// Setting the filter for the URL "/google/login".
.addFilterAt(filter(), BasicAuthenticationFilter.class)
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
/*This method for creating filter for OAuth authentication.*/
private OAuth2ClientAuthenticationProcessingFilter filter() {
//Creating the filter for "/google/login" url
OAuth2ClientAuthenticationProcessingFilter oAuth2Filter = new OAuth2ClientAuthenticationProcessingFilter(
"/login");
//Creating the rest template for getting connected with OAuth service.
//The configuration parameters will inject while creating the bean.
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(authorizationCodeResourceDetails,
oauth2ClientContext);
oAuth2Filter.setRestTemplate(oAuth2RestTemplate);
// Setting the token service. It will help for getting the token and
// user details from the OAuth Service.
oAuth2Filter.setTokenServices(new UserInfoTokenServices(resourceServerProperties.getUserInfoUri(),
resourceServerProperties.getClientId()));
return oAuth2Filter;
}
}
The problem is you haven't configure your AuthenticationManager in your code you have done this #Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
Authentication Manager:
attempts to authenticate the passed Authentication object, returning a fully populated Authentication object (including granted authorities) if successful.
For simple in memory Authentication Manager you can do something like this;
#Autowired
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER").and().withUser("hiren").password("hiren")
.roles("ADMIN");
}
After this you can get Principal object after successful authentication of user.
You can also configure your own authentication provider like this:
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customeAuthenticationProvider);
}
this link will be useful for authentication provider configuration
Your method is a REST endpoint, meaning that the parameters coming to this function are serialized data. You need to deserialize it and get the required data from it. The parameter of this function cannot be in Priciple type, from where you sent you probably need to send it in byte[]. Then you need to convert byte[] into String, which is a JSON. Then using Jackson library you need to fill your user. After that you can obtain the e-mail of the user.
#RequestMapping("/")
protected Principal login(byte[] data) {
String inputJSONString = new String(data);
ObjectMapper mapper = new ObjectMapper();
Principle user = objectMapper.readValue(inputJSONString, Principle.class);
//Now you have a setted user object and you can get user's mail from a method like getMail()
user.getMail();
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
System.out.println(SecurityContextHolder.getContext().getAuthentication().getDetails());
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal(
System.out.println("testing testing xyz");
return user;
}