一、前言
本案例中的源代码已上传到资源库,可自行下载,传送阵 https://download.****.net/download/qq_36260963/89906196
Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置。学习框架就是学习配置
简单来说,它提供了一堆依赖打包Starter,并已经按照使用习惯解决了依赖问题—习惯大于约定。Spring Boot默认使用tomcat作为服务器,使用logback提供日志记录。无需多言,直接进入节奏.
Thymeleaf是一个流行的现代服务器端Java模板引擎,它专门设计用于Web和独立环境中的应用程序。它允许开发者以清晰和直观的方式将服务器端的数据与HTML、XML、JavaScript、CSS以及纯文本等模板文件结合起来。Thymeleaf的最大特点之一是它的“自然模板”技术,这意味着开发者可以编写标准的HTML代码,并通过Thymeleaf特有的属性和表达式(如th:text、th:if等)来动态插入或修改内容,而无需改变HTML的结构或引入特定的模板语法。
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
二、效果演示
密码错误
登录成功
三、后端代码
四、springsecurity 相关
主要是springsecurity config 配置相关,包含开启表单登录,url地址拦截与放行,退出登录;以及自定义userdetailservice 接口,实现自己的登录逻辑
4.1 springsecurity 核心配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/user/**","/","/login","/register").permitAll() //允许访问
.anyRequest().authenticated() // 其他都需要认证
.and().formLogin() // 开启表单登录
.loginProcessingUrl("/doLogin")
.usernameParameter("username")
.passwordParameter("passwd")
.loginPage("/login")
.successForwardUrl("/employee/lists")// 成功后跳转的url
.failureUrl("/login")
.and().logout()
.logoutSuccessUrl("/login")//退出登录后跳转的页面
.and().csrf().disable(); // 关闭csrf 防护
}
}
4.2 用户登录相关
@Service
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired(required = false)
private UserMapper userMapper;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public User login(String username, String password) throws IllegalAccessException {
User queryUsr = userMapper.findByUserName(username);
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
throw new IllegalAccessException("用户名和密码不能为空");
}
if (ObjectUtils.isEmpty(queryUsr)) {
throw new IllegalAccessException("用户不存在");
}
String encPwd = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
logger.info("原密码:{},加密后的密码为:{}",password,encPwd);
// 密码对比DigestUtils
if (!encPwd.equals(queryUsr.getPassword())) {
throw new IllegalAccessException("密码错误!");
}
return queryUsr;
}
@Override
public void addUser(User user) throws IllegalAccessException{
if (!StringUtils.hasLength(user.getUsername()) || !StringUtils.hasLength(user.getPassword())) {
throw new IllegalAccessException("用户名和密码不能为空");
}
//查看用户名是否重复
User queryUser = userMapper.findByUserName(user.getUsername());
if (!ObjectUtils.isEmpty(queryUser)) {
throw new IllegalAccessException("用户【"+user.getUsername()+"】已存在,请更换用户名!");
}
String password = user.getPassword();
String encPwd = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
user.setPassword(encPwd);
//添加
userMapper.save(user);
logger.info("添加用户成功!");
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!StringUtils.hasLength(username) ) {
throw new UsernameNotFoundException("用户名不能为空");
}
User queryUsr = userMapper.findByUserName(username);
if (ObjectUtils.isEmpty(queryUsr)) {
throw new UsernameNotFoundException("用户不存在");
}
LoginSessionUser sessionUser = new LoginSessionUser(queryUsr);
return sessionUser;
}
}
4.3 userdetail 实体类
public class LoginSessionUser implements UserDetails {
private User user;
public LoginSessionUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_amin"));
}
@Override
public String getPassword() {
return "{MD5}"+user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
五、spring mvc 配置
用来配置静态资源拦截,常规url 以及viername配置
5.1 mvc 核心配置
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Value("${photo.file.dir}")
private String dir;
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//viewController 请求路径 viewName: 跳转视图
registry.addViewController("/").setViewName("redirect:/login");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/register").setViewName("regist");
registry.addViewController("/addEmp").setViewName("addEmp");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/**")
.addResourceLocations("file:"+dir);
}
}
六、yml 配置相关
1、数据库相关配置
2、日志配置
3、静态文件配置
4、模版解析器 thymeleaf 配置
server: port: 8082 spring: ## 数据库配置 datasource: driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource username: root password: 123456 url: jdbc:mysql://localhost:3306/springboot_ems_db?characterEncoding=UTF-8 web: #静态文件配置 resources: static-locations: classpath:/static/,file:${photo.file.dir} mvc: static-path-pattern: /static/** # thymeleaf 模版配置 thymeleaf: cache: false suffix: .html prefix: classpath:/templates/ mode: html # mybatis 配置 mybatis: mapper-locations: classpath:/mapper/mysql/*.xml type-aliases-package: com.fashion.entity # 日志配置 logging: level: com.fashion: debug photo: file: dir: j:\java\project\springboot-study\springboot-ems-security\images\
七、前端相关页面
这里只是展示部分thymeleaf 的部分,因为过多页面,需要可自行下载zip文件包
7.1 登录页面
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
<span th:text="${#dates.format(#dates.createNow(), 'yyyy-MM-dd HH:mm:ss')}"/>
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a th:href="@{/login }">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎进入,请登录!
<!--<span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION }" style="color: red;"/>
<span th:text="${session.errMsg }" style="color: deeppink;"/>-->
<span th:if="${session.SPRING_SECURITY_LAST_EXCEPTION != null}" th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message }" style="color: deeppink;"/>
</h1>
<form th:action="@{/doLogin }" method="post">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<td valign="middle" align="right">
用户名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" name="username" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
密码:
</td>
<td valign="middle" align="left">
<input type="password" class="inputgri" name="passwd" />
</td>
</tr>
</table>
<p>
<input type="submit" class="button" value="点我登录 »" />
<a th:href="@{/register}">还没有账号,立即注册</a>
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>
7.2 登录成页面
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity5">
<head>
<title>emplist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
<span th:text="${#dates.format(#dates.createNow(), 'yyyy-MM-dd HH:mm:ss')}"/>
<br />
<span sec:authorize="isAuthenticated()">
<a th:href="@{/logout }">安全退出</a>
</span>
</p>
</div>
<div id="topheader">
<h1 id="title">
<a th:href="@{/employee/lists }">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
欢迎
<span sec:authorize="isAuthenticated()">
<span sec:authentication="principal.username"></span>
</span>
</h1>
<table class="table">
<tr class="table_header">
<td>
编号
</td>
<td>
姓名
</td>
<td>
头像
</td>
<td>
工资
</td>
<td>
生日
</td>
<td>
操作
</td>
</tr>
<tr th:each="emp,status:${employeeList }" th:class="${status.odd ? 'row1' : 'row2'}">
<td>
<span th:text="${emp.id }"/>
</td>
<td>
<span th:text="${emp.name }"/>
</td>
<td>
<img th:src="@{/ }+${emp.photo}" width="60">
</td>
<td>
<span th:text="${emp.salary }"/>
</td>
<td>
<span th:text="${#dates.format(emp.birthday,'yyyy年MM月hh日')}"/>
</td>
<td>
<a href="javascript:;" th:onclick="'delFn('+${emp.id}+');'">删除</a> <a href="javascript:;" th:onclick="'updFn('+${emp.id }+');'">更新</a>
</td>
</tr>
</tr>
</table>
<script type="text/javascript">
function delFn(id) {
if (confirm("你真的要删除员工id为:"+id+"的记录吗?")) {
location.href = '[[@{/employee/delEmployee?id= }]]'+id;
}
}
function updFn(id) {
location.href = '[[@{/employee/getDetail?id= }]]'+id;
}
</script>
<p>
<!--<input type="button" class="button" value="添加" onclick="location='addEmp.html'"/>-->
<input type="button" class="button" value="添加" onclick="addEmp()"/>
<script type="text/javascript">
function addEmp() {
location.href = '[[@{/addEmp}]]'
}
</script>
</p>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>