Using postman, I can get a list of users with a get request to: http://localhost:8080/users.
But when I send a post request to the same address, I get a 403 error.
#RestController
public class UserResource {
#Autowired
private UserRepository userRepository;
#GetMapping("/users")
public List<User> retrievaAllUsers() {
return userRepository.findAll();
}
#PostMapping("/users")
public ResponseEntity<Object> createUser(#RequestBody User user) {
User savedUser = userRepository.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
}
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
/*#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}*/
/*#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("/users/**").hasRole("ADMIN")
.and().csrf().disable().headers().frameOptions().disable();
}*/
}
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue
private Long id;
private String name;
private String password;
#Enumerated(EnumType.STRING)
private Role role;
// TODO which cna be removed
public User() {
super();
}
public User(Long id, String name, String password, Role role) {
this.id = id;
this.name = name;
this.password = password;
this.role = role;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
INSERT INTO user VALUES (1, 'user1', 'pass1', 'ADMIN');
INSERT INTO user VALUES (2, 'user2', 'pass2', 'USER');
INSERT INTO user VALUES (3,'user3', 'pass3', 'ADMIN')
EDIT
EDit 2
added delete, but it also gives a 403?
#DeleteMapping("/users/{id}")
public void deleteUser(#PathVariable long id) {
userRepository.deleteById(id);
}
edit 4
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users/**").permitAll();
}
}
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#EnableWebSecurity enables spring security and it by default enables csrf support, you must disable it in order to prevent 403 errors.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
Or send csrf token with each request.
Note: disabling csrf makes application less secure, best thing to do is send csrf token.
When you use spring boot with spring security and if you are accessing your API's(POST, PUT, DELETE) from Postman or something, they wont be accessible and error is related to authorization like forbidden 403.
So in that case, you have to disabled to csrf functionality to run and test the API from Postman.
The answer provided by #benjamin c is right. You have to add the class with the this configuration will work.
Make sure you are removing this when you add your code in production. CSRF protection is must and you have to keep it in security functionality.
I am just extending his answer for more details by providing complete class details. My requirement was to just test the API from Postman, so I added this class, and able to test the API from Postman.
But after that I have added Spring Junit classes to test my functionalities and removed this class.
#Configuration
#EnableWebSecurity
public class AppWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
}
Hope this helps to someone.
403 means you don't have authorization. Even though you commented out your method, your code will still be preconfigured with default security access.
You can add:
http.authorizeRequests()
.antMatchers("/users/**").permitAll();
UPDATE : The configuration with csrf disabled:
http.csrf()
.ignoringAntMatchers("/users/**")
.and()
.authorizeRequests()
.antMatchers("/users/**").permitAll();
This configuration in SecurityConfig class helped me solve it:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
Please configure your http like this ;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//configureothers if u wants.
.csrf().disable();
}
Please read for more CSRF
Related
hi what i trying to achieve is to protect a url that only one role can access to it, when i try add .hasRole("USER"), still the other role can access it. Here is how i do it :
here is my controller :
#RestController
#RequestMapping("/couponapi")
public class CouponController {
#Autowired
CouponRepository couponRepository;
#PostMapping("/coupons")
public Coupon save(#RequestBody Coupon coupon) {
return couponRepository.save(coupon);
}
#GetMapping("/coupons/{code}")
public Coupon findByCode(#PathVariable("code") String code) {
return couponRepository.findByCode(code);
}
#GetMapping("/something")
public Coupon findByCodeX() {
return couponRepository.findByCode("SUPERSALE");
}
}
i want to protect #GetMapping("/something") only for ROLE_ADMIN, here is how my Spring Security Configuration looked like :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailServiceImpl userDetailService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/couponapi/coupons/**").hasRole("USER")
.antMatchers(HttpMethod.POST,"/couponapi/coupons/**").hasRole("USER")
.antMatchers("/couponapi/something").hasRole("ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
here is my role class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles implements GrantedAuthority {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
#Override
public String getAuthority() {
return null;
}
}
and here is my service that implements UserDetailsService class :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), users.getRoles());
}
}
and here is my database role data :
as you can see i have ROLE_USER and ROLE_ADMIN
and here is my joined database
** i just updated my question and i have answer of half of my issue, please read my answer below to see the latest issue
In spring security the most restrictive rules are defined first, therefore your configuration should look like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/**/something").hasRole("USER")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
i find the culprit here, but not entirely i still missing the functional of HttpMethod one. here is how i fixed the role, in my Role class i do a mistake that i trying to implements the GrantedAuthority class, that the thing that cause this trouble (without HttpMethod issue). and here is how i fixed it
first, delete the impements and turn the role into usual #Entity class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
}
then at class that implements UserDetailsService, add this code :
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
i cannot explain it in details, but i think List only need 1 string, that's string is a role name. then here is the full code :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), grantedAuthorities);
}
}
if anyone can find out the fix for this line .antMatchers(HttpMethod.GET,"/**/something").hasRole("USER") which is i want to use HttpMethod to differentiate each method with the same url, if someone have the answer, i will accept your answer. waiting for it
thanks for your good presentation of your problem.
That’s what I underestood, you want to give access only to user with Admin role to this URL /couponapi/something and give access to this URL /couponapi/coupons/** for all authenticated users whatever their roles (ADMIN or USER)
Try use hasAuthority instead of hasRole and delete the first line http.httpBasic() , it worked for me.
So in the WebSecurityConfig.java file:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/couponapi/something").hasAuthority("ROLE_ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
Then In the above section of code, you only give access to users with ADMIN authority to access this url /couponapi/something so user with USER authority can’t access it.
And since you have declared a password encoder bean, you can use it with the AuthenticationManagerBuilder
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
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.
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
I have implemented spring security in my application.
It is stateless-token based authentication and username/password based authentication.
I have configured user authentication, but the role-based authorisation is not working.
A user who has ROLE_USER is able to access the controller method which has ROLE_ADMIN.
Here is the configuration.
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Bean
AuthenticationProvider passwordBasedAuthenticationProvider() {
return new PasswordBasedAuthenticationProvider();
}
#Bean
AuthenticationProvider tokenBasedAuthenticationProvider(){
return new TokenBasedAuthenticationProvider();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/public/**");
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().
anyRequest().authenticated().
and().
anonymous().disable();
http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(passwordBasedAuthenticationProvider()).
authenticationProvider(tokenBasedAuthenticationProvider());
}
}
DOMAINS
#Entity
public class Role implements GrantedAuthority {
private long id;
private String authority;
}
public class User implements UserDetails{
private String username;
private String passwordHash;
private Role role;
}
#RestController
public class TesController {
#RequestMapping(value="/authController")
#Secured("ROLE_ADMIN")
String test(){ return "I am secure for ROLE_ADMIN"}
}
What is incorrect about this configuration?
You have to define at least the RoleHierarchie with something like this or whatever the configuration may look like in your case:
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF");
r.setHierarchy("ROLE_STAFF > ROLE_USER");
r.setHierarchy("ROLE_DEVELOPER > ROLE_USER");
r.setHierarchy("ROLE_USER > ROLE_GUEST");
return r;
}
I try to build an application based on Spring Boot (1.2.7.RELEASE) and Vaadin (7.6.3). My problem is that I'm not able to integrate Spring Security with Vaadin. I want a custom Vaadin built LoginScreen and Spring Security control. My project setup is as follows:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
}
And here is my Vaadin login UI
#SpringUI(path = "/login")
#Title("LoginPage")
#Theme("valo")
public class LoginUI extends UI {
TextField user;
PasswordField password;
Button loginButton = new Button("Login", this::loginButtonClick);
private static final String username = "username";
private static final String passwordValue = "test123";
#Override
protected void init(VaadinRequest request) {
setSizeFull();
user = new TextField("User:");
user.setWidth("300px");
user.setRequired(true);
user.setInputPrompt("Your username");
password = new PasswordField("Password:");
password.setWidth("300px");
password.setRequired(true);
password.setValue("");
password.setNullRepresentation("");
VerticalLayout fields = new VerticalLayout(user, password, loginButton);
fields.setCaption("Please login to access the application");
fields.setSpacing(true);
fields.setMargin(new MarginInfo(true, true, true, false));
fields.setSizeUndefined();
VerticalLayout uiLayout = new VerticalLayout(fields);
uiLayout.setSizeFull();
uiLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
setStyleName(Reindeer.LAYOUT_BLUE);
setFocusedComponent(user);
setContent(uiLayout);
}
public void loginButtonClick(Button.ClickEvent e) {
//authorize/authenticate user
//tell spring that my user is authenticated and dispatch to my mainUI
}
}
When I start my application spring redirects me to my login UI, which is fine.
But I don't know how to authenticate the user against the spring security mechanism and dispatch to my mainUI.
I'm also facing the problem with csrf tokens, if I don't disable csrf I'll get the crfs token is null exception. I found a lot of examples handling those problems but there is no solution provided with Vaadin.
Thanks for help.
After a week of struggle and research, I was able to get this working. It was very exhausting because there a lot of information and solutions on the internet, most of them using xml based configuration or JSP form based login, until now I couldn't find another solution without a xml config file using the Vaadin framework to create a custom login page.
I cannot guarantee that this is best practice or the easiest solution. Moreover I didn't evaluate every part of it, the login mechanism works as far as I can see but maybe there could be some problems which I haven't discovered yet.
Maybe it'll help someone who face the same problem so I'll post my answer here.
First of all my securityConfig:
#Resource(name = "authService")
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
#Bean
public DaoAuthenticationProvider createDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
You have to disable crsf but that's no problem since vaadin has its own crsf protection.
Furthermore you need to permit some URIs so vaadin can access its resources: /VAADIN/** is absolutely necessary, I would also reccommend to allow /vaadinServlet/**, /PUSH/** and /HEARTBEAT/**, but it depends on what parts of Vaadin you use.
Second my UserDetailsService:
#Service("authService")
public class AuthService implements UserDetailsService {
#Autowired
CustomUserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
CustomUser user = userRepository.findCustomUserByUserName(userName);
return user;
}
}
The DaoAuthenticationProvider uses the UserDetails' loadUserByUserName method to get an object of a class which implements the UserDetails interface. Be aware that every attribute described in the UserDetailsInterface must not be null, otherwise you get a NullPointerException thrown by the DaoAuthenticationProvider later.
I created a JPA Entity which implements the UserDetails interface:
#Entity
public class CustomUser implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToMany(fetch = FetchType.EAGER)
Collection<Authorities> authorities;
String password;
String userName;
Boolean accountNonExpired;
Boolean accountNonLocked;
Boolean credentialsNonExpired;
Boolean enabled;
#Autowired
#Transient
BCryptPasswordEncoder passwordEncoder;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return userName;
}
#Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
#Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
#Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
#Override
public boolean isEnabled() {
return enabled;
}
public void setId(Long id) {
this.id = id;
}
public void setAuthorities(Collection<Authorities> authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = passwordEncoder.encode(password);
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
Plus the Authorities Entity:
#Entity
public class Authorities implements GrantedAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String authority;
#Override
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
}
Obviously you'll have to store some user data in the database first before the authentication will work.
In Vaadin I couldn't get it worked by using one UI with different views, so I ended up using two UIs one for login and another for the main application.
In Vaadin I could set the URI path in the class annotation:
#SpringUI(path = "/login")
#Title("LoginPage")
#Theme("valo")
public class LoginUI extends UI {
//...
}
With this configuration my login screen is available at localhost:port/login
and my main application at localhost:port/main.
I login the user programmatically within a button.click method in my loginUI:
Authentication auth = new UsernamePasswordAuthenticationToken(userName.getValue(),password.getValue());
Authentication authenticated = daoAuthenticationProvider.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authenticated);
//redirect to main application
getPage().setLocation("/main");
I hope it helped some of you.
As an alternative to the given approach, you can also rely on vaadin4spring, an 'unofficial' set of extensions to further integrate Vaadin and Spring.
It currently offers two ways to integrate Spring Security, which seem to work fine (even if they do not work for you, the code samples should give you enough insights to write custom integration code).