前一篇文章讲解了spring security配置自定义登录页面,而登录所需要的用户名和密码是在配置文件中设置的,而在实际开发中,我们希望的是用户和权限信息在数据库中。那么验证就需要通过数据库,本文继续沿用上一篇的案例,主要讲解通过数据库进行验证。(为了方便测试就不查询数据库啦,我们new一个User~)
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sunyuqi</groupId>
<artifactId>springboot-sercurity-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-sercurity-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
前端页面
均放在templates目录下
登录页面login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
用户名或密码错误
</div>
<form action="" method="post">
<div><label> 账号 : <input type="text" name="username"/> </label></div>
<div><label> 密码 : <input type="password" name="password"/> </label></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>你好 <span th:text="${username}"></span></h3>
<button type="button" onclick="window.location='/dologout'">登出</button>
</body>
</html>
controller
package com.sunyuqi.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class UserController {
@RequestMapping("/user/index")
public String index(Authentication authentication,Model model){
model.addAttribute("username",authentication.getName());
return "index";
}
/**
* 登录页面
* @return
*/
@RequestMapping("/login")
public String login(){
return "login";
}
/**
* 登出
* @param request
* @param response
* @param model
* @return
*/
@RequestMapping({"/dologout"})
public String logout(HttpServletRequest request, HttpServletResponse response,Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {//清除认证
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login";
}
}
User实体类
package com.sunyuqi.pojo;
import java.util.List;
public class User {
private Long userId;
private String username;
private String sex;
private String password;
private List<String> roles;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", sex='" + sex + '\'' +
", password='" + password + '\'' +
", roles=" + roles +
'}';
}
}
security config
该配置了自定义登录页面,自定义登录校验等…
我们还设置了/user这个路径下的页面需要user权限
package com.sunyuqi.config;
import com.sunyuqi.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService userDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
// 设置权限
.antMatchers("/user/**").hasAnyRole("admin", "user")
.anyRequest().authenticated()
.and().httpBasic().and()
// 默认登录页面
.formLogin().loginPage("/login")
// 默认登录成功页面
.defaultSuccessUrl("/user/index")
// 登录失败跳转
.failureForwardUrl("/login?error")
.permitAll()
.and().logout()
.logoutUrl("/dologout")
.logoutSuccessUrl("/login");
}
/**
* 添加 UserDetailsService, 实现自定义登录校验
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
// 从数据库读取的用户进行身份认证
.userDetailsService(userDetailService)
.passwordEncoder(passwordEncoder());
}
/**
* 密码加密
*/
@Bean
public PasswordEncoder passwordEncoder(){
// 使用BCrypt加密密码
return new BCryptPasswordEncoder();
}
}
MyUserDetailService
自定义登录校验需要我们实现UserDetailService接口
该类中我们设置的登录用户名为zhangsan,密码为qwe123,权限为user(实际开发中是要查询数据库的)
package com.sunyuqi.service;
import com.sunyuqi.pojo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class MyUserDetailService implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(MyUserDetailService.class);
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LOGGER.info("对用户 [{}] 进行信息加载...",username);
/* 从数据库里面查出用户 */
User user = new User();
user.setUserId(1L);
user.setUsername("zhangsan");
user.setPassword(passwordEncoder.encode("qwe123"));
user.setSex("男");
ArrayList<String> roles = new ArrayList<>();
roles.add("ROLE_user");
user.setRoles(roles);
if(user==null){
LOGGER.error("用户 [{}] 未找到",username);
throw new UsernameNotFoundException("Username:["+username+"] not found");
}
LOGGER.info("用户 [{}] 信息加载完成",username);
// SecurityUser 实现UserDetails
return new SecurityUser(user);
}
}
loadUserByUsername需要返回一个 UserDetails接口,我们新建一个类实现该接口
SecurityUser
package com.sunyuqi.service;
import com.sunyuqi.pojo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SecurityUser extends User implements UserDetails {
private static final Logger log = LoggerFactory.getLogger(SecurityUser.class);
SecurityUser(User user){
if (user != null){
this.setUserId(user.getUserId());
this.setUsername(user.getUsername());
this.setSex(user.getSex());
this.setPassword(user.getPassword());
this.setRoles(user.getRoles());
}
}
/**
* 权限集合
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<String> roles = this.getRoles();
if(roles != null){
for (String role : roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role);
authorities.add(authority);
}
}
log.info("获取登录用户已具有的权限:{}", authorities.toString());
return authorities;
}
/**
* 指示用户的账户是否已过期。无法验证过期的账户。
* @return 如果用户的账户有效(即未过期),则返回true,如果不在有效就返回false
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指示用户是锁定还是解锁。无法对锁定的用户进行身份验证。
* @return 如果用户未被锁定,则返回true,否则返回false
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示用户的凭证(密码)是否已过期。过期的凭证阻止身份验证
* @return 如果用户的凭证有效(即未过期),则返回true
* 如果不在有效(即过期),则返回false
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 指示用户是启用还是禁用。无法对禁用的用户进行身份验证
* @return 如果启用了用户,则返回true,否则返回false
*/
@Override
public boolean isEnabled() {
return true;
}
}
引导类
package com.sunyuqi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootSercurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSercurityDemoApplication.class, args);
}
}
运行项目,访问http://localhost:8080/login
输入我们设置的用户名和密码,登录成功,同时因为该用户赋予了user权限,可以访问/user路径下的index页面