1.导入核心依赖
<!--thymeleaf-springsecurity整合-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<!--SpringSecurity起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
//使用数据库账号密码登录(数据库动态管理用户、角色、权限)基于mybatis
//如果不设置自己的登录和注销会跳到默认的登录页面和注销功能
//默认的方法
sql表
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`perms` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `role_user`;
CREATE TABLE `role_user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`user_id` int(20) NOT NULL,
`role_id` int(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
application.yml配置
spring:
thymeleaf:
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url:
username:
password:
#指定数据源
type: com.alibaba.druid.pool.DruidDataSource
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedstatements: true
#配置监控统计拦截的filters, stat: 监控统计、log4j: 日志记录、wall: 防御Isql注入
#如果允许时报错java. Lang. ClassNotFoundException: org. apache. Log4j. Priority
#则导入,Log4j 依赖即可,Maven地址: https://mvnrepository. com/artifact/Log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
main:
allow-bean-definition-overriding: true
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.lwz.entity
实体类
//用户表
package com.lwz.entity;
import java.util.List;
public class User {
private int id;
private String username;
private String password;
private String perms;
private List<Role> roles;
public User() {
}
public User(int id, String username, String password, String perms, List<Role> roles) {
this.id = id;
this.username = username;
this.password = password;
this.perms = perms;
this.roles = roles;
}
public int getId() {
return id;
}
public void setId(int 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 getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", perms='" + perms + '\'' +
", roles=" + roles +
'}';
}
}
//角色表
package com.lwz.entity;
import java.util.List;
public class Role {
private int id;
private String name;
private List<User> users;
public Role() {
}
public Role(int id, String name, List<User> users) {
this.id = id;
this.name = name;
this.users = users;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", users=" + users +
'}';
}
}
Mapper接口
@Repository
public interface UserMapper {
@Autowired
public User findUserByUname(String name);
}
XML
<mapper namespace="com.lwz.mapper.UserMapper">
<resultMap id="user_role" type="com.lwz.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<collection property="roles" ofType="com.lwz.entity.Role" >
<id property="id" column="rid" />
<result property="name" column="name"/>
collection>
resultMap>
<select id="findUserByUname" resultMap="user_role" parameterType="string">
SELECT u.*,r.id as rid,r.`name` from `user` u
LEFT JOIN role_user ru ON u.id=ru.user_id
LEFT JOIN role r ON ru.role_id=r.id
where u.username=#{name}
select>
mapper>
此处省略Service层
自定义UserDetailsService 接口
实现动态认证的核心接口
package com.lwz.service.impl;
import com.lwz.entity.Role;
import com.lwz.entity.User;
import com.lwz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
//自定义UserDetailsService 接口
@Component
public class CustomUserService implements UserDetailsService {
@Autowired
UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findUserByUname(username);
if (user==null){
throw new UsernameNotFoundException("用户名不存在");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//用于添加用户的权限。只要把用户权限添加到authorities 就万事大吉。
//一个用户对应多个角色
for (Role role:user.getRoles()){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);
}
}
自定义登录成功处理’
package com.lwz.config;
import com.lwz.entity.User;
import com.lwz.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录成功处理
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
public static final String RETURN_TYPE = "html";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
UserMapper userMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取用户名
String username = request.getParameter("username");
//通过用户名查找用户信息
User user= userMapper.findUserByUname(username);
System.out.println(user);
if (RETURN_TYPE.equals("html")){
//把用户信息存进session里方便前端显示
request.getSession().setAttribute("user",user);
request.getRequestDispatcher("index").forward(request,response);
// redirectStrategy.sendRedirect(request,response,"index");
}
}
}
自定义登录失败处理
package com.lwz.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义登录失败处理
*/
@Component//添加为组件,方便config获取
public class MyAuthenticationFailHandler implements AuthenticationFailureHandler {
public static final String RETURN_TYPE = "html"; // 登录失败时,用来判断是返回json数据还是跳转html页面
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (RETURN_TYPE.equals("html")){
//返回msg提示错误
request.setAttribute("msg","账号或密码错误");
System.out.println("自定义登录失败执行了");
request.getRequestDispatcher("toLogin").forward(request,response);
}
}
}
SecurityConfig
package com.lwz.config;
import com.lwz.service.impl.CustomUserService;
import com.lwz.utils.MD5Util;
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;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired//注入自定义登录成功处理
MyAuthenticationSuccessHandler successHandler;
@Autowired//注入自定义登录失败处理
MyAuthenticationFailHandler failHandler;
@Bean
CustomUserService customUserService(){//注册UserDetailsService 的bean
return new CustomUserService();
}
//链式编程
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也只有对应权限的人才能访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAuthority("ROLE_USER")
.antMatchers("/level2/**").hasAuthority("ROLE_ADMIN")
.antMatchers("/level3/**").hasAuthority("ROLE_ADMIN");
//没有权限默认会到登录页面
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login").successHandler(successHandler).failureHandler(failHandler);
//注销 注销成功跳回首页
http.csrf().disable();//注销异常时加上这句
http.logout().logoutSuccessUrl("/");
//记住我(功能)
http.rememberMe().rememberMeParameter("remember");
}
//认证
//密码编码:PasswordEncoder
//在Spring Security 5.0+新增了很多的加密方法~
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*auth.userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder(){
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encrypt((String)rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5Util.encrypt((String)rawPassword));
}});//user Details Service验证 使用md5加密,这个方法登录失败会报异常,暂时无法解决
auth.userDetailsService(customUserService());*/
//从数据库中进行账号密码验证
auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
//从内存中进行账号密码验证
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("laiwenzhuo").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
// .and()
// .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
// .and()
// .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
Controller(路由Controller)
package com.lwz.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String toIndex(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/level1")
public String level1(){
return "views/level1/1";
}
@RequestMapping("/level2")
public String level2(){
return "views/level2/1";
}
@RequestMapping("/level3")
public String level3(){
return "views/level3/1";
}
}
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<a th:href="@{index}" th:fragment="toIndex">首页a>
<div th:fragment="Info">
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/toLogin}">登录a><br>
div>
<div sec:authorize="isAuthenticated()">
<a th:href="@{/logout}">注销a><br>
div>
<div sec:authorize="isAuthenticated()">
用户名:<span sec:authentication="name">span><br>
角色:<span sec:authentication="principal.authorities">span><br>
div>
div>
body>
html>
level1
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>level1title>
head>
<body>
<div th:insert="~{common::toIndex}">div>
<div th:insert="~{common::Info}">div>
<hr>
<p>这是vip1访问的页面p>
body>
html>
index.html
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<p>这是首页p>
<div th:if="${!#strings.isEmpty(session.user)}">
id:<p th:text="${session.user.id}">p>
username:<p th:text="${session.user.username}">p>
password:<p th:text="${session.user.password}">p>
roles:<p th:each="role:${session.user.roles}" th:text="${role}">p><br>
div>
<div th:insert="~{common::Info}">div>
<hr>
<div sec:authorize="hasRole('ROLE_USER')">
<a th:href="@{/level1}">访问vip1a>div><br>
<hr>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<a th:href="@{/level2}">访问vip2a>div><br>
<hr>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<a th:href="@{/level3}">访问vip3a>div><br>
<hr>
body>
html>
login.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页title>
head>
<body>
<form th:action="@{/login}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="checkbox" name="remember">记住我
<input type="submit" value="登录"> <br>
form>
body>
html>
效果:
当未登录时不显示指定元素
账号密码错误
当登录后获得权限:显示有权限的模块
参考:
https://blog.csdn.net/shenjianxz/article/details/82348986
https://blog.csdn.net/lizc_lizc/article/details/84059004