I created a pure Spring web application for learning purposes. It uses a simple database, where there are two roles: “Admin” and “User”. It has a /login part where I can log in. Based on the role the user is forwarded to different pages. With “User” role it is a simple greeting page where the user can logout, and that’s it. With “Admin” role the user get a list of all the users and he can edit or delete them, or create a new user. The adding and editing happens with using the same form and it is at /add_or_edit, this is used by its controller.
The goal would be that /add_or_edit is restricted to “Admin” usage only. Right now I can reach it without logging in, and also with “User” role. I could use a simple WebFilter to redirect to /login or user home based on the content of the session but I would like to reach something similar with Spring security authorization. The Spring security logging / authentication seems to work fine but I wasn’t able to create a good one for authorization.
I created a SecurityConfig class:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/add_or_edit", "/add_or_edit/**").hasRole("Admin")
.antMatchers("/home","/home/**").hasAnyRole("User", "Admin")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
It is added to a WebAppInitializer (extends AbstractAnnotationConfigDispatcherServletInitializer) .
Here, at configure I also tried to just prohibit GET and POST before.
For UserDetails I use custom-made wrapper class, this one:
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRole() == null ?
Collections.emptyList() :
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().getName()));
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getLogin();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
The LoginController is this one:
@Controller
public class LoginController {
private static final Logger logger = LogManager.getLogger(LoginController.class);
@Autowired
private AuthenticationManager authenticationManager;
@GetMapping("/login")
public String showLoginPage() {
return "login";
}
@PostMapping("/login")
public String handleLogin(@RequestParam("login") String login,
@RequestParam("password") String password,
HttpSession session) {
try {
// Create an Authentication object
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(login, password));
// Authentication successful, set UserDetails in session if needed
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
session.setAttribute(USER, userDetails.getUser());
// Redirect to home page after successful login
return "redirect:/home";
} catch (Exception e) {
logger.error(e.getMessage(), e);
session.setAttribute("error", "Login or Password is not appropriate. Please try it again.");
return "login";
}
}
}
And this is the CustomUserDetailsService:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByLogin(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
System.out.println("User " + username + " found with role: " + user.getRole().getName());
CustomUserDetails customUserDetails = new CustomUserDetails(user);
customUserDetails.getAuthorities().forEach(authority -> {
System.out.println("Authority: " + authority.getAuthority());
});
return new CustomUserDetails(user);
}
}
Please consider the fact that it is my first attempt to do something like this. I’m 100 percents sure there are many faulty things there. I thought these are maybe enough to find my fault but if I can provide anything that could be useful here, please let me know.
Thank you very much for your help in advance.