Spring Security - Implementing UserDetailsService - java

I understand that Spring will pass the principal into controller's method if I include it as a method parameter.
I'm trying to extend this functionality by implementing UserDetailsService:
I created a class named CustomUserDetails that extends org.springframework.security.core.userdetails.User
I created a service named CustomUserDetailsService that implements UserDetailsService
Exception
HTTP Status 500 - Request processing failed; nested exception is
java.lang.ClassCastException:
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
cannot be cast to com.demo.model.CustomUserDetails
The following line in my controller method is throwing the exception:
CustomUserDetails userDetails = (CustomUserDetails) principal;
Controller.java
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Principal principal, HttpServletRequest request) {
// Throws exception here
CustomUserDetails userDetails = (CustomUserDetails) principal;
System.out.println(userDetails.getFirstName());
// Tried this and it also throws exception
// User cannot be cast to CustomUserDetails
//Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//CustomUserDetails userDetails = (CustomUserDetails)auth.getPrincipal();
// Render template located at
// src/main/resources/templates/dashboard.html
modelAndView.setViewName("dashboard");
return modelAndView;
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private DataSource dataSource;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Autowired
SecurityHandler successHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().usersByUsernameQuery(usersQuery).authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/register*").permitAll()
.antMatchers("/reset").permitAll().antMatchers("/forgot").permitAll().antMatchers("/grid").permitAll()
.antMatchers("/login").permitAll().antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/login").failureUrl("/login?error")
.defaultSuccessUrl("/dashboard").successHandler(successHandler).usernameParameter("email")
.passwordParameter("password").and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout").and().exceptionHandling().accessDeniedPage("/access-denied");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/error**", "/resources/**", "/static/**", "/css/**", "/js/**", "/img/**");
}
}
CustomUserDetails.java
public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
public CustomUserDetails(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
CustomUserDetailsService.java
#Service
public class CustomUserDetailsService implements UserDetailsService{
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
if(StringUtils.isEmpty(userName))
throw new UsernameNotFoundException("User name is empty");
//if you don't use authority based security, just add empty set
Set<GrantedAuthority> authorities = new HashSet<>();
CustomUserDetails userDetails = new CustomUserDetails(userName, "", authorities);
userDetails.setFirstName("Testing: " + new Date());
return userDetails;
}
}

in WebSecurityConfigurerAdapter, you need add your register custom detail service:
auth.userDetailsService(customDetailService)
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().usersByUsernameQuery(usersQuery).authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource).passwordEncoder(bCryptPasswordEncoder);
}

Change your controller to:
CONTROLLER
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Authentication authentication, HttpServletRequest request) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal;
Note it returns a Authentication object.

Related

Spring security keeps returning 401 Unauthorized code despite correct credentials provided

After configuring a custom user details service that uses an embedded H2 database to retrieve user credentials, I keep receiving 401 Unauthorized errors when completing a post command even though the username and password are available. I don't know if this makes any difference, but the console still prints out the generated security password (although the auto generated credentials return the same 401 error). Please take a look at my code below and let me know any suggestions or fixes available.
User model...
#Entity
public class ApplicationUser {
#Id
private Long id;
private String username, password, role;
public ApplicationUser(String username, String role, String password) {
this.username=username;
this.password=password;
this.role=role;
}
public ApplicationUser() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
#Override
public String toString() {
return "ApplicationUser{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}
User repository class...
#Repository
public class AppUserRepository {
#Autowired
JdbcTemplate jdbcTemplate;
static class ApplicationUserRowMapper implements RowMapper<ApplicationUser> {
#Override
public ApplicationUser mapRow(ResultSet rs, int rowNum) throws SQLException {
ApplicationUser applicationUser = new ApplicationUser();
applicationUser.setUsername(rs.getString("username"));
applicationUser.setPassword(rs.getString("password"));
applicationUser.setRole(rs.getString("userrole"));
return applicationUser;
}
}
public List<ApplicationUser> findAll() {
return jdbcTemplate.query("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ur.username",
new ApplicationUserRowMapper());
}
public ApplicationUser findByUsername(String username) {
return jdbcTemplate.queryForObject("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ? and u.username = ur.username",
new Object[] {username}, new ApplicationUserRowMapper());
}
}
Security config class...
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = "security_package")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password");
}
}
Custom authentication provider...
#Service
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
AppUserRepository userRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
ApplicationUser user = userRepository.findByUsername(name);
if(user == null) {
throw new BadCredentialsException("Authentication failed.");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole()));
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Main app class...
#SpringBootApplication(exclude={
HazelcastAutoConfiguration.class,
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class
})
#EnableScheduling
#Slf4j
public class AwsorchestratorApplication implements CommandLineRunner{
#Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
if ( System.getProperty("spring.profiles.active") == null )
{
System.setProperty("spring.profiles.active","local");
}
new SpringApplication(AwsorchestratorApplication.class).run(args);
}
class ExitException extends RuntimeException implements ExitCodeGenerator {
private static final long serialVersionUID = 1L;
#Override
public int getExitCode() {
return 10;
}
}
}
UPDATE ONE::...
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private AppUserRepository userRepo;
#Override
public UserDetails loadUserByUsername(String username) {
ApplicationUser user = userRepo.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User '" + username + "' not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
return new User(user.getUsername(), user.getPassword(), Collections.singletonList(grantedAuthority));
}
}
I was able to correct this issue by removing my custom authentication class (although I don't think this was actually part of the problem), and editing my web security config to match the below.
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = package_location)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public CustomUserDetailsService detailsService;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
//the password encoder here is only to support a mix of encoding - that change can be removed
auth.userDetailsService(detailsService).passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/awsorchestrator/**")
.hasAnyRole("ADMIN", "USER")
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.and()
.csrf()
.disable();
}
}
The important part that directly correlated to my problem was the passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()) portion. I also had to change the password stored in the database to be prefixed with {bcrypt} and now I am able to login with no issues.
Thanks R.G for the tips.

Java Spring Multiple #Autowired MongoRepository Usages with JWT

The successfulAuthentication function in JWTAuthenticationFilter.java gives a nullpointerexception. Do you see why this would be an issue? Is it a problem with using the same bean for autowiring?
This is my current project structure:
-com
-register
-RegisterController.java
-security
-JWTAuthenticationFilter.java
-JWTAuthorizationFilter.java
-SecurityConstants.java
-WebSecurity.java
-user
-User.java
-UserRepository.java
-UserService.java
-Application.java
Application.java
#Configuration
#SpringBootApplication
public class Application {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
UserRepository.java
#Repository
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String name);
User findByEmail(String Email);
User findBy_id(ObjectId id);
}
UserService.java
#Service
public class UserService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.findByUsername(username);
if(user == null)
return null;
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority("user"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
public User getUserByUsername(String username) {
return this.userRepository.findByUsername(username);
}
public User getUserBy_id(ObjectId _id) {
return userRepository.findBy_id(_id);
}
public void saveUser(User newUser){
userRepository.save(newUser);
}
}
User.java
#Document
public final class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ObjectId _id;
private String email;
private String username;
private String password;
private AccountProperties accountProperties;
private Address address;
private List<Pet> pets = new ArrayList<>();
private String phoneNumber;
public User() {}
public User(#JsonProperty("email") String email, #JsonProperty("username") String username,
#JsonProperty("password") String password) {
this.email = email;
this.username = username;
this.password = password;
}
public String get_id() { return _id.toHexString();}
getters and setters() ...
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private UserRepository userRepo;
private AuthenticationManager authenticationManager;
JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String username = ((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername();
String token = JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
User u = uRepo.findByUsername("admin");
res.getWriter().write(
"{\"" + SecurityConstants.HEADER_STRING + "\":\"" + SecurityConstants.TOKEN_PREFIX+token + "\"," +
"\"" + "ObjectID" + "\":\"" + u.get_id() + "\"}"
);
}
}
JWTAuthorizationFilter.java
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
WebSecurity.java
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userDetailsService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS );
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(this.userDetailsService).passwordEncoder(this.bCryptPasswordEncoder);}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
SecurityConstants.java
public class SecurityConstants {
public static final String SECRET = "SecretKeyToGenJWTs";
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/users/sign-up";
}
RegisterController.java
#RestController
#RequestMapping("/users")
public class RegisterController {
#Autowired
private UserService userService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#PostMapping("/sign-up")
public void signUp(#RequestBody User user) {
if (user.getPassword() == null || user.getUsername() == null)
return;
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userService.saveUser(user);
}
}
Not sure if this is the root cause of your issue, but I have never seen #Configuration in the main application. I would try to move that to a separate config class and see if that helps
Annotate the JWTAuthenticationFilter with #Component or add #Bean in configuration file . Looks like the object is not created
The problem is that you doesn't define JWTAuthenticationFilter as Bean, so spring doesn't inject dependencies in it.
You can get beans in filter manualy. From the GenericFilterBean javadoc:
This generic filter base class has no dependency on the Spring org.springframework.context.ApplicationContext concept. Filters usually don't load their own context but rather access service beans from the Spring root application context, accessible via the filter's ServletContext (see org.springframework.web.context.support.WebApplicationContextUtils).
Or you can make it bean. But if you are using Spring Boot consider that:
The fact that all filters internal to Spring Security are unknown to the container is important, especially in a Spring Boot application, where all #Beans of type Filter are registered automatically with the container by default. So if you want to add a custom filter to the security chain, you need to either not make it a #Bean or wrap it in a FilterRegistrationBean that explicitly disables the container registration.

How to restrict routes in spring security dynamically?

Good day,
I am using spring security to restrict user, and i am using mongodb.
I have created UserDetail and userDetail Services.
her is my webSecurity config.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService mongoUserDetails() {
return new CustomUserDetailsService();
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(mongoUserDetails());
authProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/static/unsecure/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
I am storing permissions in database like this.
"permissions" : [
"/reports/report1",
"/reports/report2"
]
and here is userDetail service.
public class MongoUserDetails implements UserDetails {
private final String username;
private final String password;
private final List<GrantedAuthority> grantedAuthorities;
private final boolean enabled;
public MongoUserDetails(String username, String password, String[] authorities, boolean isEnabled) {
this.username = username;
this.password = password;
this.grantedAuthorities = AuthorityUtils.createAuthorityList(authorities);
this.enabled = isEnabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
in private final List grantedAuthorities; I have successfully stored authorites. which are "/reports/report1", "/reports/report2"
When i run my web application it is redirecting my to login page and after valid credentials it redirects my to /hello page.
Now i am creating controllers and actions like
#Controller
#ResponseBody
public class MyController {
#GetMapping("/user/greeting")
public String greeting(HttpServletRequest request){
return "welcome secure";
}
#GetMapping("/login1")
public String greeting1(HttpServletRequest request){
}
}
so since List grantedAuthorities; does not contains this route it should not allowed the user for this action
how can i achieve this. assuming all authorities are dynamic.
thanks.
My example:
Authentication service:
#Service
public class AuthService implements UserDetailsService {
#Autowired
private UserJpaRepository userJpaRepository;
#Autowired
private UserProfileJPARepository profileJPARepository;
#Autowired
private UserProfileContainer profileContainer;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = (User) userJpaRepository.findByLogin(username);
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
UserDetails userDetails = (UserDetails) new SecureUser(
user.getId(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
user.getMobileNumber(),
user.getLogin(),
user.getPassword(),
user.isActive(),
true,
true,
true,
Arrays.asList(authority)
);
user.setLastLogonTime(new Timestamp((new Date()).getTime()));
userJpaRepository.saveAndFlush(user);
profileContainer.setUser(user);
return userDetails;
}
}
Session scoped bean:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserProfileContainer {
private User user = null;
public UserProfileContainer() {
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
And in User class you cal store a List<String> allowedURLS = new ArrayList<>();
AFTER EDIT:
I've wrote this example. In this case I have some SecureUser.class which extends org.springframework.security.core.userdetails.User and in this class I have Set<String> allowedPathSet actually HashSet. And here is the custom filter:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomerFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// nothing here...
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(true);
SecurityContextImpl sci = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
if (sci != null) {
SecureUser user = (SecureUser) sci.getAuthentication().getPrincipal();
String url = req.getRequestURI();
if (!user.path.contains("url")) ((HttpServletResponse) response).sendRedirect("http://redirect-URL-Here/");
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
This example isn't a silver bullet but the main idea is present here and it work.

How to configure security access in Spring?

I'm learning Spring and now trying to configure security. I need to create secured endpoints and one not secured for registration.
But when I'm trying to access http://localhost:8080/register I'm getting error "An Authentication object was not found in the SecurityContext" and "AuthenticationCredentialsNotFoundException".
I have repeated example in docs but still get this error.
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/register").permitAll();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OAuth2Config.java
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Value("3600")
private int expiration;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
configurer.authenticationManager(authenticationManager);
configurer.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("app").secret("secret").accessTokenValiditySeconds(expiration)
.scopes("read", "write").authorizedGrantTypes("password", "refresh_token").resourceIds("resource");
}
}
RegisterController.java
#RestController
public class RegisterController {
#Autowired
UserDao userDao;
#Autowired
CityDao cityDao;
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> addUser(#RequestParam(value = "email") String email, #RequestParam(value = "firstName") String firstName,
#RequestParam(value = "lastName") String lastName, #RequestParam(value = "city") Long cityId,
#RequestParam(value = "password") String password){
User userToFind = userDao.findByEmail(email);
City city = cityDao.findById(cityId).get();
if (userToFind != null) {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
User user = new User(firstName, lastName, email, city, passwordEncoder.encode(password));
return new ResponseEntity<>(userDao.save(user), HttpStatus.CREATED);
}
else{
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
}
}

Get authenticated user

I'm implementing a project using Spring security oauth2, everything works perfectly, now I want to start digging deeper beyond the basics. I want to check if the user making the request is the actual user owner of the resource, the end result would be for example:
/private/users/{uuid}/clients returns all clients for the specified user.
So my controller now looks like this:
#RestController
public class HomeController {
#Autowired
private UserService userService;
#GetMapping(value = "/")
public String index() {
return "Hello world";
}
#GetMapping(value = "/private")
public String privateTest(Principal principal) {
User user = userService.get(principal.getName());
return user.getUuid();
}
}
EDIT: The full security code (working) for a better explanation.
ResourceServerConfig
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/","/home","/register","/login").permitAll()
.antMatchers("/private/**").authenticated();
}
}
CustomUserDetails with getters and setters off course
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String uuid;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.uuid = user.getUuid();
this.authorities = translate(user.getRoles());
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
Main
#SpringBootApplication
public class DummyOauthApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(DummyOauthApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count() == 0) {
service.save(new User("user", "password", UUID.randomUUID().toString(), Arrays.asList(new Role("USER"), new Role("ACTUATOR"))));
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
So, using the way I've implemented. I can get the actual user but it implies a database query every time an endpoint is called. Getting the user and match with the user uuid.
I want to find another way that I can get the user and then compare if the uuid = user.getUuid()
Thanks in advance.
After some time and a lot of mistakes, I've managed to find a solution that I leave here. The CustomUserDetails can be seen in the question and from there you can easily get the uuid and match with the requested one.
public static CustomUserDetails getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
return (CustomUserDetails) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
EDIT: if you want to return the user you do something like this
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private User user;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.user = user;
this.authorities = translate(user.getRoles());
}
}
And then in a Utils or something,
public static User getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return userDetails.getUser();
}
}
throw new IllegalStateException("User not found!");
}
Thanks for all the effort.
Perhaps you could implement a custom AuthenticationProvider and store user details as Principal
Spring Security Authentication Provider

Categories