Udemy Spring boot course: Section 8 Spring MVC Security
- Git repo
- App goal
- Custom Access Denied page
- Display based on Role
- Using JDBC authentication for the username,password roles
- Make spring security use custom tables for authentication
Git repo
App goal
Example
@Configuration File
- first method is the in memory username and password storage
- will use db for authentication in the future
- Second method specififies the configuration for the Web MVC security
- comments explain what each line of code does
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
// in memory configuration
@Bean
public UserDetailsService users() {
UserDetails john = User.builder()
.username("john")
.password("{noop}test123")
.roles("EMPLOYEE")
.build();
UserDetails mary = User.builder()
.username("mary")
.password("{noop}test123")
.roles("EMPLOYEE", "MANAGER")
.build();
UserDetails susan = User.builder()
.username("susan")
.password("{noop}test123")
.roles("EMPLOYEE", "MANAGER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(john, mary, susan);
}
// security filter chain configuration
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// enable csrf with the defaults
.csrf(Customizer.withDefaults())
// authorize all request that are authenticate
.authorizeHttpRequests(
authorize -> authorize.anyRequest().authenticated()
)
// uses http basic auth
.httpBasic(Customizer.withDefaults())
// Tells it to use the default login form page
// .formLogin(Customizer.withDefaults());
// uses custom login form
.formLogin(
form ->
// login page
form.loginPage("/showMyLoginPage")
// where the login page is posted to, we don't have to worry about this,
// ... spring will take care and create this automagically for us in the background
.loginProcessingUrl("/authenticateTheUser")
// allow everybody to see the login page
.permitAll()
);
// return the http build for the spring security filter chain
return http.build();
}
}
HTML Custom Login Page
- Using custom Login page but somethings like below have to have a certain structure
- username has to be named username
- password has to be named password
- We get the /autenticateTheUser for free since Spring will create that automatically for us since we specified it in the @Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login page</title>
</head>
<body>
<h2>Login Form</h2>
<!-- Goes to the route we specified in the SecurityConfiguration.java -->
<form th:action="@{/authenticateTheUser}" method="POST">
<p>
<!-- name must be username when using spring security -->
User name: <input type="text" name="username">
</p>
<p>
<!-- name must be password when using spring security -->
Password: <input type="text" name="password">
</p>
<input type="submit" value="Login">
</form>
</body>
</html>
Simple Demo Controller
- / is the home route
- /showMyLoginPage is the login page
- all unauthenticated requested get routed here first to get authenticated (specified in the @Configuration)
1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class DemoController {
@GetMapping("/")
public String home(){
return "home";
}
@GetMapping("/showMyLoginPage")
public String loginPage(){
return "security/plain-login";
}
}
Show the user incorrect password and/or password
- if the login fails, I want to let the user know that it had failed
- By default spring will append the query parameter ?error to the end of the url and redirect back to the login page
- We can show the error message by just doing the following in the html form
This is all we have to do show the error message
- param is a keyword to get any of the query parameters from the url
- We are saying if there is a param.query in the url we will show this message between the div
1
2
3
4
<!-- show error if there is any -->
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
Logging out a user
- the /logout is provided by Spring for free
- when a user is logged out the following will happen
- invalidate user http session and remove session cookies
- send the user back to the login page
- append the ?logout query parameter to the login form route
Getting the username of the currently logged in user
- You can get the username of the user that is currently logged in with the code below
1
Username: <span sec:authentication="principal.username"></span>
Getting the roles of the currently logged in user
- You can get the roles of the currenly logged in user with the code below
1
Role(s): <span sec:authentication='principal.authorities'></span>
Restricting URL’s to Roles
- Boilerplate code for restricting urls to certain roles
requestMatchers("/").hasRole("EMPLOYEE")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize ->
authorize
// restrict urls based on roles
.requestMatchers("/").hasRole("EMPLOYEE")
.requestMatchers("/leaders/**").hasRole("MANAGER")
.requestMatchers("/systems/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// other stuff
return http.build();
}
Custom Access Denied page
- We can have our own custom access denied page instead of the default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// here it is
.exceptionHandling(configurer ->
configurer
// this is the route /access-denied
.accessDeniedPage("/access-denied")
)
// other stuff
return http.build();
}
Display based on Role
- We want to display links only if the currently logged in user has a specific role
sec:authorize="hasRole('MANAGER')"
1
2
3
4
<!-- Add a link to point to /leaders -->
<p sec:authorize="hasRole('MANAGER')">
<a th:href="@{/leaders}">Leaders page (for managers only)</a>
</p>
Using JDBC authentication for the username,password roles
- Spring uses these tables by default
- users
- username
- password
- enabled
- authorities (AKA Roles)
- username
- authority
- users
ROLE_ROLEName by default
- By default spring uses the format ROLE_ROLEName
- So we have to be mindful of this when we insert our roles into the authorities table
- it should look something like this …
Make spring security use custom tables for authentication
- we will try to use jdbc authentication with our custom tables
- here are our tables
- members
- user_id
- ps
- active
- roles
- user_id
- role
- members
Here is the code to make springboot aware of the custom tables with the custom queries
- This code goes in your @Configuration file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// connect to the mysql db for authentication
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource){
// gets the datasource created by spring by default
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
// finds the user by the username
jdbcUserDetailsManager
.setUsersByUsernameQuery("SELECT user_id, pw, active FROM members WHERE user_id=?");
// finds the roles for the user by the username
jdbcUserDetailsManager
.setAuthoritiesByUsernameQuery("SELECT user_id, role FROM roles WHERE user_id=?");
return jdbcUserDetailsManager;
}
This post is licensed under CC BY 4.0 by the author.