项目demo链接:https://pan.baidu.com/s/1GMgDXr5Ca9kYtp6agkRi4w 提取码:j3sn
复制这段内容后打开百度网盘手机App,操作更方便哦
一,数据库设计,省略(jpa运行项目会自动创建,到第六步)
二,创建spring boot 项目
坐标 + 项目名称
下一步,在下一步
Spring-boot版本改2.0.5,薪版数据连接方式不一致
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.apache.shiro
shiro-spring
1.4.0
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-starter-test
test
配置jpa + mysql 数据链接等等
Yml 文件添加jpa数据支持
自行修改数据库密码
spring:
devtools:
restart:
enabled: false
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_shiro
hikari: # springboot 2.0 整合了hikari ,据说这是目前性能最好的java数据库连接池
username: root
password: root
jpa:
hibernate:
ddl-auto: update # 第一次建表create 后面用update,要不然每次重启都会新建表
show-sql: true #日志中显示sql语句
# 分页配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
三、创建entity + Mapper
User类
大家自己生成set,get 方法
@Entity
public class User implements Serializable {
@Id
@GeneratedValue
private Integer uid;
@Column(unique =true)
private String username;//帐号
private String password; //密码;
@ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name = "UserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List roleList;// 一个用户具有多个角色
/* private String salt;//加密密码的盐
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
*/
// 省略 get set 方法
}
Role 角色类,大家自己生成set,get 方法
@Entity
public class Role {
@Id
@GeneratedValue
private Integer id; // 编号
private String description; // 角色描述,UI界面显示使用
//角色 -- 权限关系:多对多关系;
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
private List permissions;
// 用户 - 角色关系定义;
@ManyToMany
@JoinTable(name="UserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
private List users;// 一个角色对应多个用户
/*private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户*/
// 省略 get set 方法
}
Permission 权限类,大家自己生成set,get 方法
@Entity
public class Permission implements Serializable {
@Id
@GeneratedValue
private Integer id;//主键.
private String name;//名称.
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
/* private String url;//资源路径.
private Long parentId; //父编号
private Boolean available = Boolean.FALSE;
private String parentIds; //父编号列表
@Column(columnDefinition="enum('menu','button')")
private String resourceType;//资源类型,[menu|button]*/
@ManyToMany
@JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
private List roles;
// 省略 get set 方法
}
四、Dao 层 mapper
说明:
/*
* 我们在这里直接继承 JpaRepository
* 这里面已经有很多现成的方法
* 这也是JPA的一大优点
*
* (1) CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
(2) PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
(3)JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
(4)JpaSpecificationExecutor: 实现条件查询
* */
*
UserMapper
/**
* Created by Administrator on 2018/10/2/002.
* JpaRepository 说明:
* User 实体类
* Long 实体类主建的数据类型
*/
@Component
public interface UserMapper extends JpaRepository, JpaSpecificationExecutor {
//根据id 查询用户对象
@Query(value = "SELECT * FROM USER WHERE id = ?1", nativeQuery = true)
User selectId(Integer id);
//根据用户名查询用户是否存在
@Query(value = "SELECT * FROM USER WHERE username = ?1", nativeQuery = true)
User selectName(String username);
}
RoleMapper
@Component
public interface RoleMapper extends JpaRepository, JpaSpecificationExecutor {
//查询所有角色
@Query(value = "SELECT * FROM description", nativeQuery = true)
List selectDescription();
}
PermissionMapper
@Component
public interface PermissionMapper extends JpaRepository, JpaSpecificationExecutor {
//查询所有的权限
@Query(value = "SELECT permission FROM permission", nativeQuery = true)
List selectPermission();
}
五、Controller 控制层
LoginContoller
@Controller
public class LoginContoller {
//====================== 首页 ==============================
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index() {
return "/index";
}
//=============== 登陆操作。post请求会自动拦截到权限shiro 认证方法 ===================
@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) throws Exception{
// 登录失败从request中获取shiro处理的异常信息。 shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
model.addAttribute("mgs","账号不存在");
System.out.println("UnknownAccountException -- > 账号不存在:");
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
model.addAttribute("mgs","密码不正确");
System.out.println("UnknownAccountException -- > 密码不正确:");
} else if ("kaptchaValidateFailed".equals(exception)) {
model.addAttribute("mgs","验证码错误");
System.out.println("UnknownAccountException -- > 验证码错误:");
} else {
model.addAttribute("mgs","其他异常");
}
}
// 此方法不处理登录成功,由shiro进行处理,成功自动跳到上一个路径
return "/login";
}
}
UserController 类
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return "user/add";
}
@RequestMapping(value = "/main", method = RequestMethod.GET)
public String main() {return "user/main"; }
@RequestMapping(value = "/update", method = RequestMethod.GET)
public String upadte() {
return "user/update";
}
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public String delete() {
return "user/delete";
}
}
Index页面
hello,登陆成功
用户管理
权限加载到数据库
查看列表
添加
修改
删除
退出系统
Error 页面
没有权限
User下的增删改查页
自己修改了
查看数据
添加数据
删除数据
修改数据
先试一下项目正常不了,运行项目会自己创建数据库
springboot + jpa 配置完成了
下面开始配置shiro,
七、创建 ShiroConfig 配置文件
hashedCredentialsMatcher 密码凭证先住掉,配置了密码加密加盐在使用,
不然看不懂无法登陆系统,因为登陆密码加密
同时把myShiroRealm(hashedCredentialsMatcher()) 里面的对象去掉 myShiroRealm()
http://localhost:8080/index
会被sliro拦截到并跳转到 login 页面,并保存该路径
登录错误会执行login方法
登录成功跳转到之前输入的url地址方法
import com.example.springbootshiro.ShiroRealm.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
/*======================== 安全管理器 =================================== */
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//使用了密码凭证使用,并注调上一行
// securityManager.setRealm(myShiroRealm(hashedCredentialsMatcher()));
return securityManager;
}
/*======================== 密码凭证器 =================================== */
/* @Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512等。
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,默认1次, 设置两次相当于 md5(md5(""));
return hashedCredentialsMatcher;
}*/
/*=================== 自定义 realm=====================*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
//使用了密码凭证使用下列方法
/* @Bean
public MyShiroRealm myShiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myShiroRealm;
}*/
/*================= Filter工厂,设置对应的过滤条件和跳转条件 ========================= */
//
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map map = new HashMap();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/*================= 加入注解的使用,不加入这个注解不生效 ========================= */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
八、创建Realm(认证与授权) MyShiroRealm类
/*
* shiro 认证、授权类
* */
public class MyShiroRealm extends AuthorizingRealm {
/**
* 注意此处需要添加@Lazy注解,否则SysUserService缓存注解、事务注解不生效
*/
@Autowired
@Lazy
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
//============================ 授权 =======================================
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取SimpleAuthorizationInfo 对象信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取户信息
User user = (User)principals.getPrimaryPrincipal();
if(user.getUsername().equals("admin")){
//所有权限
info.addStringPermission("*:*");
}else {
//获取用户角色
for(Role role : user.getRoleList()){
//把角色加入到 authorizationInfo 对象中
info.addRole(role.getDescription());
//获取角色权限
for(Permission permission : role.getPermissions()){
//把权限加入到 authorizationInfo 对象中
info.addStringPermission(permission.getPermission());
}
}
}
return info;
}
/*当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(stringPermissions);*/
//静态授权 list集合,分支判断是某某用户登录,添加该用户权限到list集合,info.addStringPermission(list)
//============================ 认证方法 =======================================
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)//authenticationToken
throws AuthenticationException {
//获取用户信息
String username = (String)token.getPrincipal();
//获取数据库信息
User user = (User) userMapper.selectName(username);
//数据空返回空,无任何权限
if(user == null){return null; }
//密码判断,shiro处理(用户对象 ,密码,加盐(用户名,),自定义的Realm的名称),
// 注:可不加盐,需于登陆,注册账号配置一致,无密码凭证去掉第3个属性
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()) ;
/*SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), getName()) ;*/
return info;
}
/*
* 1, 获取token中用户的输入的账号 --> username
* 2, 通过username从数据库中查找 User对象
* 3, User对象不存在,验证失败返回空
* 4, 创建SimpleAuthenticationInfo,把用户对象 ,密码,自定义的Realm的名称放入进行秘密认证
* 5、登录认证成功自动跳转到输入的 url,失败到login方法
* 实际项目中,User对象可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
* */
}
把认证方法内容注掉返回空,看看是不是登陆链接是不是进不去了,呵呵
认证方法逻辑根据需求更改
打开认证,会自己匹配数据库密码,不匹配返回到 login 方法,返回错误信息
匹配成功自动跳到输入的url 链接
启动项目输入 localhost:8080/index 进入login 拦截到登陆页 保存index 路径
此时数据库无数据,会拦截到logn 页面
点击登陆会拦截到login 方法,提示账号密码错误信息
数据库添加数据登陆认证
登陆会执行reaim 认证方法,根据用户名(dao层自己写的sql) 查询数据库是否存在改用户,不存在返回 null,跳到 login方法拦截异常返回前台相关错误信息,无账号此时后台会报出UnknownAccountException 异常
,存在继续下走,判断密码是否正常,shiro 后台处理,数据库密码加了盐,需要添加第3属性,密码错误后台会报出IncorrectCredentialsException 异常
此时输入数据库的账号密码 登陆成功。
九、对方法添加权限控制(授权)
创建 PermissionName 注解支持 接口类
/*
* shior保存权限的名称的注解支持类
* */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionName {
String value();
}
Usercontroller 改为
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.GET)
@RequiresPermissions("user:add")
@PermissionName("用户添加")
public String add() {
return "user/add";
}
@RequestMapping(value = "/main", method = RequestMethod.GET)
@RequiresPermissions("user:main")
@PermissionName("用户查看")
public String main() {return "user/main"; }
@RequestMapping(value = "/update", method = RequestMethod.GET)
@RequiresPermissions("user:update")
@PermissionName("用户修改")
public String upadte() {
return "user/update";
}
@RequestMapping(value = "/delete", method = RequestMethod.GET)
@PermissionName("用户删除")
@RequiresPermissions("user:delete")
public String delete() {
return "user/delete";
}
}
十、一键生成所有权限到数据库
根据springMvc的方法获取权限名称
根据自定义的权限名称name 同步保存
可以根据业务自行生成
PermissionController
/*
* 权限加载类
* */
@Controller
@RequestMapping("/permission")
public class PermissionController {
@Autowired
private PermissionMapper permissionMapper;
//springMvc请求映射处理器
@Autowired
RequestMappingHandlerMapping pmhm = new RequestMappingHandlerMapping();
@RequestMapping("/reload")
public String reload() {
//查询所有权限
final List permissionList = permissionMapper.selectPermission();
//获取所有controller中有映射标签 @RequestMapping 的方法
Map handlerMethods = pmhm.getHandlerMethods();
Collection methods = handlerMethods.values();
//遍历标签找到有权限标签 @RequiresPermissions 的方法
for (HandlerMethod method : methods) {
RequiresPermissions methodAnnotation = method.getMethodAnnotation(RequiresPermissions.class);
if (methodAnnotation != null) {
//获得具体权限,封装到permission对象
String permissionValue = methodAnnotation.value()[0];
//如果所有权限中包含了该权限,跳出本次循环
if(permissionList.contains(permissionValue)){
continue;
}
//权限添加数据库
Permission permission = new Permission();
permission.setPermission(permissionValue); //权限
permission.setName(method.getMethodAnnotation(PermissionName.class).value()); //权限名称
permissionMapper.save(permission);
}
}
return "/index";
}
}
前台url 加载权限按钮 链接生成,走的dao 层自定义sql 语句
数据库自动生成了相关权限
现在这里的方法都有了权限控制
User 的方法现在都不能访问了,会跳到配置的错误页 error
在数据库-设置权限
下面是我的表数据
权限表
用户表
角色表
用户角色中间表(管理员又1.修改 2 删除. 3添加 4,查看的权限,不需要设置)
设置张三 开发+测试
李四 测试
角色与权限中间表,
测试只有查看权限,开发有增删改查权限
Zhangsan是开发+测试 = 有了增删改查 权限
Lisi 是测试 = 只有查看 权限
开始测试,先退出系统,或重启服务器
使用zhangsan 登陆,可以访问增删改查方法,每点击一次访问,都会进入授权方法
然后我们在退出系统
使用 lisi 登陆,发现除了查看,增删改都没权限了,并跳到error 提示页
如果出了问题自行检查授权方法是否存在逻辑问题,dobug
到这里,权限配置就完成了
十一、密码加密加盐方法MD5_Shiro类
建立工具类 MD5_Shiro
public class MD5_Shiro {
/**
* 随机生成 salt 需要指定 它的字符串的长度
*
* @param len 字符串的长度
* @return salt
*/
public static String generateSalt(int len) {
//一个Byte占两个字节
int byteLen = len >> 1;
SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator();
return secureRandom.nextBytes(byteLen).toHex();
}
/**
* 获取加密后的密码,使用默认hash迭代的次数 1 次
*
* @param hashAlgorithm hash算法名称 MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512、etc。
* @param password 需要加密的密码
* @param salt 盐
* @return 加密后的密码
*/
public static String encryptPassword(String hashAlgorithm, String password, String salt) {
return encryptPassword(hashAlgorithm, password, salt, 1);
}
/**
* 获取加密后的密码,需要指定 hash迭代的次数
*
* @param hashAlgorithm hash算法名称 MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512、etc。
* @param password 需要加密的密码
* @param salt 盐
* @param hashIterations hash迭代的次数
* @return 加密后的密码
*/
public static String encryptPassword(String hashAlgorithm, String password, String salt, int hashIterations) {
SimpleHash hash = new SimpleHash(hashAlgorithm, password, salt, hashIterations);
return hash.toString();
}
测试类test,生成 zhangsan 用户密文保存手动复制保存到数据库
这里的加盐规则是MD5 密码+用户名,MD5重复加密1次,
MD5(MD5(密码+用户名)),用户名就是盐,可以自定义
@Test
public void contextLoads() {
String str = MD5_Shiro.encryptPassword("MD5","123456","zhangsan",1);
System.out.println(str);
}
获取密文手动复制保存到数据库
复制到数据库
此时张三都无法登陆系统了,提示密码错误
我们要修改ShiroConfig 类打开密码凭证
ShiroConfig 配置类修改为
@Configuration
public class ShiroConfig {
/*======================== 安全管理器 =================================== */
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager.setRealm(myShiroRealm());
//使用了密码凭证使用,并注调上一行
securityManager.setRealm(myShiroRealm(hashedCredentialsMatcher()));
return securityManager;
}
/*======================== 密码凭证器 =================================== */
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512等。
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,默认1次, 设置两次相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/*=================== 自定义 realm=====================*/
/* @Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}*/
//使用了密码凭证使用下列方法
@Bean
public MyShiroRealm myShiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myShiroRealm;
}
/*================= Filter工厂,设置对应的过滤条件和跳转条件 ========================= */
//
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map map = new HashMap();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/*================= 加入注解的使用,不加入这个注解不生效 ========================= */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
认证方法 Realm 的认证方法加密加盐
密码凭证器 和 认证方法的加盐次数,加密方式等需一致
此时admin 和lisi 用户登陆密码不正确,因为数据库密码没加密
而 zhangsan 可以登陆,
注意,加密规则配置config 的密码凭证 必须与 MD5_Shiro.encryptPassword的一致
Ok,shiro 配置就完成了,缓存和记住我大家可以自己研究研究,
谢谢观看----