Spring Boot 集成 Spring Security的简单应用,从数据库读取数据校验用户,页面使用Thymeleaf模板
项目地址 https://github.com/helloworlde/SpringSecurity
演示 http://project.hellowood.com.cn/Security/
创建 Spring Boot 应用
添加依赖
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
runtime('mysql:mysql-connector-java')
runtime('org.springframework.boot:spring-boot-starter-tomcat')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
创建用户表并插入数据
CREATE TABLE user (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(45) NOT NULL,
password VARCHAR(45) NOT NULL,
enabled INT NOT NULL DEFAULT 1
);
INSERT INTO user (username, password, enabled) VALUES ('username', 'password', TRUE);
添加配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/security?useSSL=false
spring.datasource.username=security
spring.datasource.password=security
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.type-aliases-package=cn.com.hellowood.springsecurity.mapper
mybatis.mapper-locations=mappers/**Mapper.xml
添加 Security 配置文件
import cn.com.hellowood.springsecurity.security.CustomAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 所有请求均可访问
http.authorizeRequests()
.antMatchers("/", "/login", "/login-error", "/css/**", "/index")
.permitAll();
// 其余所有请求均需要权限
http.authorizeRequests()
.anyRequest()
.authenticated();
// 配置登录页面的表单 action 必须是 '/login', 用户名和密码的参数名必须是 'username' 和 'password',
// 登录失败的 url 是 '/login-error'
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.failureUrl("/login-error");
}
/**
* Configure global.
*
* @param auth the auth
* @throws Exception the exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
// 使用自定义的 Authentication Provider
auth.authenticationProvider(customAuthenticationProvider);
}
}
添加自定义的 Authentication Provider 类
import cn.com.hellowood.springsecurity.model.UserModel;
import cn.com.hellowood.springsecurity.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private HttpSession session;
@Autowired
private UserService userService;
/**
* Validate user info is correct form database
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// 检查用户名密码是否正确
UserModel user = userService.loadUserByUsernameAndPassword(username, password);
if (user == null) {
logger.error("{} login failed, username or password is wrong", username);
throw new BadCredentialsException("Username or password is not correct");
} else if (!user.getEnabled()) {
throw new AccountExpiredException("Account had expired");
}
// 用户信息有效时将其放入 session 中
session.setAttribute("user", user);
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, grantedAuthorities);
return auth;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
添加校验用户信息所需要的类
- 添加 UserModel.java
public class UserModel {
private Integer id;
private String username;
private String password;
private Boolean enabled;
/**
* Instantiates a new User model.
*/
public UserModel() {
}
/**
* Instantiates a new User model.
*
* @param id the id
* @param username the username
* @param password the password
* @param enabled the enabled
*/
public UserModel(Integer id, String username, String password, Boolean enabled) {
this.id = id;
this.username = username;
this.password = password;
this.enabled = enabled;
}
/**
* Gets id.
*
* @return the id
*/
public Integer getId() {
return id;
}
/**
* Sets id.
*
* @param id the id
*/
public void setId(Integer id) {
this.id = id;
}
/**
* Gets username.
*
* @return the username
*/
public String getUsername() {
return username;
}
/**
* Sets username.
*
* @param username the username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Gets password.
*
* @return the password
*/
public String getPassword() {
return password;
}
/**
* Sets password.
*
* @param password the password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Gets enabled.
*
* @return the enabled
*/
public Boolean getEnabled() {
return enabled;
}
/**
* Sets enabled.
*
* @param enabled the enabled
*/
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return "UserModel{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", enabled=" + enabled +
'}';
}
}
- 添加 UserService.java
import cn.com.hellowood.springsecurity.mapper.UserMapper;
import cn.com.hellowood.springsecurity.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* Load user by username and password user model.
*
* @param username the username
* @param password the password
* @return the user model
*/
public UserModel loadUserByUsernameAndPassword(String username, String password) {
return userMapper.getUserByUsernameAndPassword(username, password);
}
}
- 添加 UserMapper.java
import cn.com.hellowood.springsecurity.model.UserModel;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
/**
* Gets user by username and password.
*
* @param username the username
* @param password the password
* @return the user by username and password
*/
UserModel getUserByUsernameAndPassword(@Param("username") String username,
@Param("password") String password);
}
- 添加 UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.com.hellowood.springsecurity.mapper.UserMapper">
<resultMap id="baseResultMap" type="cn.com.hellowood.springsecurity.model.UserModel">
<id column="id" property="id" javaType="java.lang.Integer" jdbcType="INTEGER"></id>
<result column="username" property="username" javaType="java.lang.String" jdbcType="VARCHAR"></result>
<result column="password" property="password" javaType="java.lang.String" jdbcType="VARCHAR"></result>
<result column="enabled" property="enabled" javaType="java.lang.Boolean" jdbcType="INTEGER"></result>
</resultMap>
<select id="getUserByUsernameAndPassword" resultType="cn.com.hellowood.springsecurity.model.UserModel">
SELECT
id,
username,
password,
enabled
FROM user
WHERE username = #{username, jdbcType=VARCHAR}
AND password = #{password, jdbcType=VARCHAR}
</select>
</mapper>
添加页面
- index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
<link rel="stylesheet" href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container">
<form action="#" class="form-signin">
<h2 class="form-signin-heading">Hello Spring Security</h2>
<h5 class="form-signin-heading content-adjust">Anyone can access this page</h5>
<div th:if="${session.user} != null">
<h5 class="form-signin-heading content-adjust">Your username is <span th:text="${session.user.username}"></span></h5>
<a href="/user/index" th:href="@{/user/index}" class="btn btn-success btn-block">To Security page</a>
</div>
<div th:if="${session.user} == null">
<a href="/index" th:href="@{/login}" class="btn btn-primary btn-block">To Login page</a>
</div>
</form>
<div th:fragment="logout" class="logout" th:if="${session.user} != null">
<form action="#" th:action="@{/logout}" method="post" class="form-signin">
<button class="btn btn-warning btn-block" type="submit">Log out</button>
</form>
</div>
</div>
</body>
</html>
- login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login page</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
<link rel="stylesheet" href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container">
<form th:action="@{/login}" method="post" class="form-signin">
<h2 class="form-signin-heading">Please sign in</h2>
<div>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username"
th:class="${loginError} ? 'form-control is-invalid' : 'form-control'" placeholder="Username"
required="required"
autofocus="autofocus"/>
<div class="invalid-feedback" th:if="${loginError}">
Wrong username or password
</div>
</div>
<div>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password"
required="required"/>
</div>
<button class="btn btn-success btn-block" type="submit">Sign in</button>
<a href="/index" th:href="@{/index}" class="btn btn-primary btn-block">Back to Home page</a>
</form>
</div>
</body>
</html>
- user/index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
<link rel="stylesheet" href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container">
<form action="#" class="form-signin">
<h2 class="form-signin-heading">Hello Spring Security</h2>
<h5 class="form-signin-heading content-adjust">Only logged in user can access this page</h5>
<div th:if="${session.user} != null">
<h5 class="form-signin-heading content-adjust">Logged user is <span th:text="${session.user.username}"></span></h5>
<a href="/index" th:href="@{/index}" class="btn btn-primary btn-block">Back to Home page</a>
</div>
</form>
<div th:substituteby="index::logout"></div>
</div>
</body>
</html>
添加 Controller
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
/**
* Root page.
*
* @return the index page url
*/
@RequestMapping("/")
public String root() {
return "redirect:/index";
}
/**
* Index page.
*
* @return the index page url
*/
@RequestMapping("/index")
public String index() {
return "index";
}
/**
* User index page.
*
* @return the user index page url
*/
@RequestMapping("/user/index")
public String userIndex() {
return "user/index";
}
/**
* Login page.
*
* @return the login page url
*/
@RequestMapping("/login")
public String login() {
return "login";
}
/**
* Login error page.
*
* @param model the model
* @return the login error page url
*/
@RequestMapping("/login-error")
public String loginError(Model model) {
model.addAttribute("loginError", true);
return "login";
}
}
启动应用,访问http://localhost:8080/user/index,此时没有登录,会被拦截并重定向到登录页面http://localhost:8080/login,输入用户名
username
和密码password
,登录成功后再次访问http://localhost:8080/user/index,此时该 url 可以正常访问,当输入错误的用户名或密码时会提示错误信息,说明 Spring Security 配置正确