关于Apache Shiro的简介不再赘述,这里我们用Shiro来替代Spring Security完成以下几个功能模块:
1.用户身份验证。 (校验用户名、密码)
2.用户角色权限验证。(根据角色来判定用户可以访问哪些页面和资源)
3.Shiro缓存。 (加入缓存,再缓存有效期内只会设置一次用户权限。)
4.记住密码。(有效期内直接登录,不需要再输入用户名密码验证)
5.验证码校验。
1.我们先把项目目录建好:
添加相关的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- mysql驱动; -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<!-- Spirng data JPA依赖; -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
这里我们用到thymeleaf模版
application.properties:
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.cache=false
########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
这里的设置主要是因为thymeleaf校验html文件的时候会特别严格,比如<input> 必须加上/
结尾,这里需要依赖nekohtml
.
index.html、userAdd.html、userDel.html、userInfo.html中只需要显示一句简单的文字即可。
login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
错误信息:<h4 style="color: red;" th:text="${msg}"></h4>
<form th:action="@{/login}" method="post">
<p>账号:<input type="text" name="username" value="admin"/></p>
<p>密码:<input type="password" name="password" value="123456"/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
我们先做一个简单的用户名密码验证和角色权限的设置。
2.在dao
包下把实体类创建起来。
这里我们需要三张表:
UserInfo: 用来存储用户的密码,用户名,加密用的盐等等信息。
SysRole: 角色表,存放所有的角色信息
SysPermission:权限表,定义了一些操作访问权限信息。
还有两张关联表(这里我们用JPA自动生成。):
SysUserRole: UserInfo和SysRole的关联表。
SysRolePermission:SysRole和SysPermission的关联表。
定义这些表是有原因的,也是前人实践的结果。每个用户会被赋予一个或多个角色。而一个角色又会被赋予一个或多个权限。这样通过校验用户的角色来判定有没有操作或访问权限。
UserInfo:
package com.example.demo.dao;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* Created by archerlj on 2017/6/30.
*/
@Entity
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Id@GeneratedValue
private long uid;//用户id;
@Column(unique=true)
private String username;//账号.
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private String password; //密码;
private String salt;//加密密码的盐
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
@ManyToMany(fetch=FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List<SysRole> roleList;// 一个用户具有多个角色
public List<SysRole> getRoleList() {
return roleList;
}
public void setRoleList(List<SysRole> roleList) {
this.roleList = roleList;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
/**
* 密码盐.
* @return
*/
public String getCredentialsSalt(){
return this.username+this.salt;
}
@Override
public String toString() {
return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
+ ", salt=" + salt + ", state=" + state + "]";
}
}
SysRole:
package com.example.demo.dao;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* Created by archerlj on 2017/6/30.
*/
@Entity
public class SysRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id@GeneratedValue
private Long id; // 编号
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private String description; // 角色描述,UI界面显示使用
private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
//角色 -- 权限关系:多对多关系;
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
private List<SysPermission> permissions;
// 用户 - 角色关系定义;
@ManyToMany
@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
private List<UserInfo> userInfos;// 一个角色对应多个用户
public List<UserInfo> getUserInfos() {
return userInfos;
}
public void setUserInfos(List<UserInfo> userInfos) {
this.userInfos = userInfos;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
}
// @Override
// public String toString() {
// return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
// + ", permissions=" + permissions + "]";
// }
}
SysPermission:
package com.example.demo.dao;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* Created by archerlj on 2017/6/30.
*/
@Entity
public class SysPermission implements Serializable {
private static final long serialVersionUID = 1L;
@Id@GeneratedValue
private long id;//主键.
private String name;//名称.
@Column(columnDefinition="enum('menu','button')")
private String resourceType;//资源类型,[menu|button]
private String url;//资源路径.
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private Long parentId; //父编号
private String parentIds; //父编号列表
private Boolean available = Boolean.FALSE;
@ManyToMany
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
private List<SysRole> roles;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getParentIds() {
return parentIds;
}
public void setParentIds(String parentIds) {
this.parentIds = parentIds;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
+ ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
+ available + ", roles=" + roles + "]";
}
}
关于密码盐:一般用用户名➕盐(随机数)来协助加密密码。
3.我们在web
包下随便建一个HomeController:
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index() {
return "index";
}
@RequestMapping("/login")
public String login() {
return "login";
}
}
这里先简单的能访问各个页面即可。
下面在mysql中创建一个数据库test
,记得修改数据库的编码格式为utf8.参考这里
启动项目,数据库表会自动建好,index和login页面也可以访问。
这节先到这里,再分一节介绍。
现在的目录应该是这样的:
SpringBoot + Shiro (一)基础工程搭建
SpringBoot + Shiro (二)身份校验和角色设置
SpringBoot + Shiro (三)权限
SpringBoot + Shiro (四)缓存&记住密码
SpringBoot + Shiro (五)验证码
最后,感谢几位作者的文章解惑:
springboot整合shiro-登录认证和权限管理
Spring Boot Shiro权限管理【从零开始学Spring Boot】
Spring boot 中使用Shiro
最后帮朋友打个小广告