I have this `application.properties' file:
security.basic.enabled=false
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/appdata
spring.datasource.username=kleber
spring.datasource.password=123456
spring.datasource.continue-on-error=true
sprinf.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.file-size-threshold=10MB
server.tomcat.max-http-post-size=10MB
and this App class:
@SpringBootApplication
@Controller
public class AppApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AppApplication.class);
}
@Bean
public WebSecurityCustomizer ignoringCustomizer() {
return (web) -> web
.ignoring()
.antMatchers("/", "/login", "/logout", "/register", "/error", "/css/**", "/js/**", "/img/**");
}
@Bean
public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
@RequestMapping(value = "/")
public String index(Model model) {
return "index";
}
@RequestMapping(value = "/login")
public String login(Model model) {
return "login";
}
@RequestMapping(value = "/register", method=RequestMethod.GET)
public String register(Model model) {
model.addAttribute("obj", new Usuario());
return "register";
}
@RequestMapping(value = "/register", method=RequestMethod.POST)
public String register(@ModelAttribute Usuario usuario) {
return "redirect:/login";
}
}
I have tried add a Bean to the class above, like that:
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() { ... }
}
@Bean PasswordEncoder passwordEncoder() {
return new PasswordEncoder() { ... }
}
but this do not work. My guess is I need some way to configure them in the method WebSecurityCustomizer ignoringCustomizer() , but looking the documentation for the class WebSecurityCustomizer I do not see any way to do that.
Anyone can give any hints of how to do that?
UPDATE #1
Searching through the official site, I found some reference documentation and blog post telling the recommended way to do some actions close to what I need, but I am still struggling to get right.
First link, it's the reference page for the deprecated class WebSecurityConfigurerAdapter, where it's said to:
Use a SecurityFilterChain Bean to configure HttpSecurity or a WebSecurityCustomizer Bean to configure WebSecurity
HttpSecurity have a method to define a UserDetailsService Bean, but how I use it in my code?
The other link it's a blog post describing the old way to do some authentication tasks, and the new recommended way. The closest examples to what I want it's in the section about JDBC Authentication and In-Memory Authentication, and both of them are based on the use of a UseDetailsManager, if I am not wrong. I also tried add a new Bean like that:
@Bean
public UserDetailsManager userDetailsManager() {
return new UserDetailsManager() { ... }
}
but does not work. What's the right way to do override the beans I want now?
UPDATE 2
I currently have this code, which is still not working properly. with this configuration, I can register a new user (which is created in the database with success), but I cannot login with this user.
@SpringBootApplication
@Controller
public class App extends SpringBootServletInitializer {
@Autowired
UsuarioDao usuarioDao;
@Autowired
CredencialDao credencialDao;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(App.class);
}
@Bean
public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers("/", "/login", "/logout", "/register", "/error", "/css/**", "/js/**", "/img/**").permitAll()
.anyRequest().authenticated()
.and()
.authenticationProvider(authProvider());
return http.build();
}
@Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService());
return provider;
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) {
System.out.println("loadUserByUsername: " + username);
return usuarioDao.findBy("username", username).get(0);
}
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(rawPassword.toString().getBytes());
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for(int i=0; i<digest.length; i++) sb.append(Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1));
return sb.toString();
} catch (Exception e) {
return null;
}
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(encode(rawPassword));
}
};
}
@RequestMapping(value = "/")
public String index(Model model) {
return "index";
}
@RequestMapping(value = "/login")
public String login(Model model) {
return "login";
}
@RequestMapping(value = "/register", method=RequestMethod.GET)
public String register(Model model) {
model.addAttribute("obj", new Usuario());
return "register";
}
@RequestMapping(value = "/register", method=RequestMethod.POST)
public String register(@ModelAttribute Usuario usuario) {
try {
usuario.setPassword(passwordEncoder().encode(usuario.getPassword()));
usuario.setCredenciais(new ArrayList<Credencial>());
usuario.getCredenciais().add(credencialDao.findBy("nome", "USER").get(0));
usuarioDao.insert(usuario);
return "login";
} catch (Exception e) {
e.printStackTrace();
return "register";
}
}
}
I think there could be several issues with your actual code.
First, due to the fact you are using form login, please, try providing the appropriate configuration when defining your filter chain, for example:
As you can see, we are indicating that the login page will be handled by your controller
/loginmapping:In addition, we indicated
/authenticateas the login processing url configuration: this will activate all the authentication stuff provided by you and Spring Security to authenticate your users.Note that you need to change your
login.htmlpage as well, because in your current implementation the username/password form is being submitted as well against/login- this is probably the cause of the problem you described in your update #2. Following the example, the form should be submitted against/authenticate:Note that we included as well a route,
/hometo request your app after a successful authentication. Yu can define inAppsomething like:And a simple HTML test page,
home.html:In order to make this work you should change an additional piece in your software. According to the way you defined your code, when Spring Security tries reading your
UsuarioGrantedAuthoritiesHibernate will issue the well known failed to lazily initialize a collection of role... because in your current implementation you are reading the credentials stored in your database but there is no session:You can probably solve the issue in different ways, especially consider using
@Transactional, but one straight forward solution could be the folllowing.First, modify your
Usuarioobject and include a transient property for storing the Spring Security granted credentials:Next, include a custom method in
UsuarioDaoin order to obtain the credentials when you have an activeSession:Please, as I mentioned, consider use
@Transactionaland Spring built-in transaction demarcation mechanisms instead of handling your transactions on your own in this way.Finally, user this new method in your
UserDetailsServiceimplementation:Instead of creating this new method in
UsuarioDaoanother, perhaps better, possibility would be creating aUserService@Servicethat wrap thisUsuarioandCredentialsinitialization process: this service would be then the one used by yourUserDetailsServiceimplementation.For completeness, this is how the
Appclass would end looking like:Probably it could be improved in different ways, but the suggested setup should allow you to successfully access your app: