崛起于Springboot2.X之Shiro企业开发实践(54)
目前Springboot搭配的各种后台管理系统基本都有shiro,下面的代码都是全的,如果麻烦,可以直接git下载也可以:https://gitee.com/mdxl/shiro.git
目录图:
下面几个步骤都是配置shiro的准备,看上去多,其实是搭建mybatis以及thymeleaf,其实就是三个接口,注册(为了添加用户信息)、登陆(实现身份认证)、权限测试(实现使用权限注解...)
1、pom依赖
<!--springboot核心依赖--><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--下面两个shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--thymeleaf前端引擎框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf对应shiro的标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
2、sql表结构
CREATE TABLE `sys_permission` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT "主键",
`permission_name` varchar(32) NOT NULL COMMENT "权限名称",
`parent_id` int(11) NOT NULL COMMENT "父编号",
`parent_ids` varchar(11) NOT NULL COMMENT "父编号列表",
`permission` varchar(64) NOT NULL COMMENT "权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view",
`resource_type` varchar(32) NOT NULL COMMENT "资源类型,[menu|button]",
`url` varchar(64) NOT NULL COMMENT "资源路径",
`level` int(10) NOT NULL COMMENT "菜单层级,1(顶级),2,3",
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT="权限表";
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT "角色id",
`role_name` varchar(32) NOT NULL COMMENT "角色标识程序中判断使用,如admin,这个是唯一的",
`available` double NOT NULL DEFAULT "1" COMMENT "是否可用,如果不可用将不会添加给用户",
`description` varchar(128) DEFAULT NULL COMMENT "描述",
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT="角色表";
CREATE TABLE `sys_role_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT "主键",
`permission_id` int(11) NOT NULL COMMENT "权限id",
`role_id` int(11) NOT NULL COMMENT "角色id",
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT="角色权限关联表";
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT "用户id",
`username` varchar(32) NOT NULL COMMENT "账号",
`name` varchar(32) DEFAULT NULL COMMENT "姓名",
`password` varchar(64) NOT NULL COMMENT "密码",
`salt` varchar(16) NOT NULL COMMENT "盐值",
`state` tinyint(4) NOT NULL DEFAULT "0" COMMENT "0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定",
`createTime` datetime NOT NULL COMMENT "创建时间",
`email` varchar(64) DEFAULT NULL COMMENT "邮箱",
`tel` varchar(32) DEFAULT NULL COMMENT "电话",
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT="用户表";
CREATE TABLE `sys_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT "主键id",
`role_id` int(11) NOT NULL COMMENT "角色id",
`user_id` int(11) NOT NULL COMMENT "用户id",
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT="用户角色关联表";
3、插入数据
插入的是一个用户的数据,如果你们想自己测试的话,先注册一个,然后根据用户编写对应你写的那个用户的角色以及相关权限(账户:admin,密码:admin)
INSERT INTO `sys_permission` VALUES ("1", "添加权限", "1", "1", "test_add", "1", "#", "1"), ("2", "删除权限", "1", "1", "test:delete", "1", "#", "1"), ("3", "修改权限", "1", "1", "test:update", "1", "#", "1");
INSERT INTO `sys_role` VALUES ("1", "admin", "1", "超级管理员"), ("2", "sys", "1", "系统管理员");
INSERT INTO `sys_role_permission` VALUES ("1", "1", "1"), ("2", "2", "1"), ("3", "3", "2");
INSERT INTO `sys_user` VALUES ("1", "admin", null, "ed1f0c71d9dcaeb860a4115602c83170", "8Tt3tP", "0", "2019-11-05 13:28:30", null, null), ("2", "mdxlcj", null, "d748162215839ff1bfae7ce2f63b97ea", "2pCDRW", "0", "2019-11-05 16:38:10", null, null);
INSERT INTO `sys_user_role` VALUES ("1", "1", "1");
4、application.properties
server.port=8071spring.mvc.view.prefix=classpath:/templates/
spring.mvc.view.suffix=.html
spring.thymeleaf.cache=false
spring.mvc.static-path-pattern=/**
spring.resources.static-locations =classpath:/static/
spring.thymeleaf.mode=LEGACYHTML5
#mysql连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
#mysql打印日志到控制台
mybatis.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl
5、Springboot启动类
在启动类上添加下面注解,方便mybatis使用。
@MapperScan({"com.mdxl.shiro.dao"})
6、mappers.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
7、编写页面
7.1、403.html,该页面是用户没有授权自动跳转的页面
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>未授权页面</h3>
</body>
</html>
7.2 error.html页面
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h3>失败页面</h3>
</body>
</html>
7.3 login.html 页面
<!DOCTYPE html><html lang="en">
<head>
<title>注册登陆页面</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/js/login.js"></script>
</head>
<body>
登陆账号:<input id="username" name="username">
输入账号:<input id="password" name="password">
<button type="button" onclick="login();">登陆</button>
<button type="button" onclick="regist();">注册</button>
</body>
</html>
7.4 success.html页面
<!DOCTYPE html><html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/js/success.js"></script>
</head>
<body>
<h3>登陆成功之后进入首页</h3>
请输入:<input id="username"/>
<shiro:hasPermission name="test_add">
<button type="button" onclick="auth();">权限测试</button>
</shiro:hasPermission>
</body>
</html>
8、js文件
8.1、login.js
// 登陆function login() {
var username = $("#username").val();
var password = $("#password").val();
$.ajax({
type: "get",
url: "/login",
data:{
"username":username,
"password":password
},
dataType:"json",
async:false,
success:function (data) {
window.location.href = "/success";
}
})
}
// 注册
function regist() {
var username = $("#username").val();
var password = $("#password").val();
$.ajax({
type: "get",
url: "/regist",
data:{
"username":username,
"password":password
},
dataType:"json",
success:function (data) {
alert(data.message);
},
error: function () {
alert("225556");
}
})
}
8.2 success.js
function auth() {var username = $("#username").val();
$.ajax({
type: "get",
url: "/auth",
data:{
"username":username
},
dataType:"json",
success:function (data) {
alert(data.message);
},
})
}
9、实体类
@Datapublic class User {
private int id;
// 登录用户名
private String username;
// 名称(昵称或者真实姓名,根据实际情况定义)
private String name;
// 密码
private String password;
// //加密密码的盐
private String salt;
//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
private byte state;
// SysUserRole 多对多 一个用户具有多个角色
private List<SysRole> roleList;
// 创建时间
private Date createTime;
// 过期日期
private Date expiredDate;
// 邮箱
private String email;
// 电话
private String tel;
}
@Datapublic class SysPermission {
// 主键
private Integer id;
// 名称
private String permissionName;
// 资源类型,[menu|button]
private String resourceType;
// 资源路径
private String url;
// 权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private String permission;
// 父编号
private Long parentId;
// 父编号列表
private String parentIds;
// 菜单层级,1(顶级),2,3
private Integer level;
private Boolean available = Boolean.FALSE;
//角色 -- 权限关系:多对多关系; SysRolePermission
private List<SysRole> roles;
}
@Datapublic class SysRole {
// 主键id
private Integer id;
// 角色标识程序中判断使用,如"admin",这个是唯一的
private String roleName;
// 角色描述,UI界面显示使用
private String description;
// 是否可用,如果不可用将不会添加给用户
private Boolean available = Boolean.TRUE;
//角色 -- 权限关系:多对多关系 SysRolePermission
private List<SysPermission> permissions;
// 用户 - 角色关系定义;
private List<User> users;
}
@Datapublic class SysRolePermission {
// 编号
private Integer id;
// 角色id
private Integer roleId;
// 权限id
private Integer permissionId;
}
@Datapublic class SysUserRole {
// 编号
private Integer id;
private Integer userId;
private Integer roleId;
}
接下来正式开始集成我们的Springboot+shiro,因为我们一个后台管理系统只能login.html登陆页面能直接访问(包含登陆接口、注册接口),所以接下来我们将需要两个shiro配置
10、ShiroConfig
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@Configuration
public class ShiroConfig {
/**
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问(记住用户和密码)
* perms:该资源必须得到资源权限才可以访问(密码验证)
* role:该资源必须得到角色权限才可以访问(VIP会员)
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
shiroFilterFactoryBean.setLoginUrl("/tologin");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//拦截器
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
// 配置登陆、注册、退出接口访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/regist", "anon");
filterChainDefinitionMap.put("/logout", "logout");
// 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了,主要这行代码必须放在所有权限设置的最后,不然会导致所有 url都被拦截 剩余的都需要认证
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器,由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
//存储散列后的密码是否为16进制
//hashedCredentialsMatcher.isStoredCredentialsHexEncoded();
return hashedCredentialsMatcher;
}
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
// 设置密码加密
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/**
* SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定的Realm。
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* *
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* * @return
*/
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
// 数据库异常处理
mappings.setProperty("DatabaseException", "databaseError");
mappings.setProperty("UnauthorizedException","/403");
//mappings.setProperty("AuthorizationException","403.html");
r.setExceptionMappings(mappings);
return r;
}
/**
* 用于thymeleaf模板使用shiro标签
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* Session Manager:会话管理
* 即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;
* 会话可以是普通JavaSE环境的,也可以是如Web环境的;
*/
/*@Bean("sessionManager")
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//设置session过期时间
sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
sessionManager.setSessionValidationSchedulerEnabled(true);
// 去掉shiro登录时url里的JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}*/
}
11、ShiroRealm
import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class ShiroRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 权限认证,包括角色以及权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 1、如果身份认证的时候没有传入User对象,这里只能取到userName,也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
User user = (User)principalCollection.getPrimaryPrincipal();
// 2、身份认证通过,user已存在,接下来则获取该用户的角色以及权限
List<SysRole> roles = userService.getUserRoleList(user.getId());
// 3、给用户添加权限以及角色
for (SysRole role: roles){
// 3.1、遍历角色,给登陆的用户添加角色,以便使用该@RequiresRoles()注解
authorizationInfo.addRole(role.getRoleName());
// 3.2、获取该角色权限,给角色添加对应的权限,以便使用@RequiresPermissions()注解
List<SysPermission> permissions = userService.getPermissionList(role.getId());
for (SysPermission p : permissions){
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/**
* 身份认证的,也就是说验证用户输入的账号和密码是否正确
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号
String userName = (String)token.getPrincipal();
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.findByUserName(userName);
if(user == null){
return null;
}
String uid = user.getPassword();
String salt = user.getSalt();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, uid, ByteSource.Util.bytes(salt),getName());
return info;
}
}
12、dao层
@Mapperpublic interface UserMapper {
@Insert({
"insert into sys_user(username,password,salt,createTime) values (#{username},#{password},#{salt},#{creat})"
})
int regist(Map<String,Object> map);
@Select({
"select id,username,password,salt from sys_user where username = #{username}"
})
@Results({
@Result(column = "id",property = "id",jdbcType = JdbcType.INTEGER),
@Result(column = "username",property = "username",jdbcType = JdbcType.VARCHAR),
@Result(column = "password",property = "password",jdbcType = JdbcType.VARCHAR),
@Result(column = "salt",property = "salt",jdbcType = JdbcType.VARCHAR)
})
User findByUserName(@Param("username") String username);
/**
* 查询一个用户多少个role
*/
@Select({
"select * from sys_role o left join sys_user_role r on o.id = r.role_id and r.user_id = #{id}"
})
@Results({
@Result(column = "id",property = "id",jdbcType = JdbcType.INTEGER),
@Result(column = "role_name",property = "roleName",jdbcType = JdbcType.VARCHAR),
@Result(column = "available",property = "available",jdbcType = JdbcType.INTEGER),
})
List<SysRole> getRoleList(@Param("id") int id);
/**
* 查询一个role下有多少权限
*/
@Select({
"SELECT s.permission_name,s.permission FROM sys_permission s LEFT JOIN sys_role_permission p ON s.id = p.permission_id AND p.role_id = #{roleId}"
})
@Results({
@Result(column = "permission_name",property = "permissionName",jdbcType = JdbcType.INTEGER),
@Result(column = "permission",property = "permission",jdbcType = JdbcType.INTEGER)
})
List<SysPermission> getPermissionList(@Param("roleId") int roleId);
}
13、service层
@Servicepublic class UserService {
@Autowired
UserMapper userMapper;
/**
* 通过账号和密码查找user
*/
public User getUserByPwd(String username, String pwd) {
return new User();
}
/**
* 通过账号查找user
*/
public User findByUserName(String userName){
return userMapper.findByUserName(userName);
}
/**
* 注册
*/
public void regist(String username,String salt,String uid){
Map<String,Object> map = new HashMap<>(8);
map.put("username",username);
map.put("password",uid);
map.put("salt",salt);
map.put("creat", new Date());
userMapper.regist(map);
}
/**
* 登陆
*/
public boolean login(String userName, String password){
// 1、获取Subject实例对象
Subject currentUser = SecurityUtils.getSubject();
// 2、判断当前用户是否登录
if (currentUser.isAuthenticated() == false) {
// 2.1、进行登陆业务逻辑
}
// 3、将用户名和密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// 4、认证 传到MyAuthorizingRealm类中的方法进行认证
currentUser.login(token);
Session session = currentUser.getSession();
session.setAttribute("userName", userName);
return true;
}
/**
* 退出
*/
public void logout(){
}
/**
* 获取用户的所有角色
*/
public List<SysRole> getUserRoleList(int userId){
return userMapper.getRoleList(userId);
}
/**
* 获取角色的所有权限
*/
public List<SysPermission> getPermissionList(int roleId){
return userMapper.getPermissionList(roleId);
}
}
14、util层
随机生成盐值
public class CommonUtil {private static final String randChars = "0123456789abcdefghigklmnopqrstuvtxyzABCDEFGHIGKLMNOPQRSTUVWXYZ";
//随机字符串
public static String getRandStr(int length, boolean isOnlyNum) {
int size = isOnlyNum ? 10 : 62;
StringBuffer hash = new StringBuffer(length);
for (int i = 0; i < length; i++) {
hash.append(randChars.charAt(new Random().nextInt(size)));
}
return hash.toString();
}
}
@Data@AllArgsConstructor
public class Result {
private boolean success;
private String code;
private String message;
private Object data;
public Result() {
this.success = true;
this.code = "200";
}
public static Result success(){
return new Result();
}
public static Result success(String msg) {
Result r = new Result();
r.setMessage(msg);
return r;
}
public static Result success(String msg, Object object) {
Result r = new Result();
r.setMessage(msg);
r.setData(object);
return r;
}
public static Result success(Object obj) {
Result r = new Result();
r.setData(obj);
return r;
}
public static Result error() {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value()+"", "系统发生错误,请重试");
}
public static Result error(String msg) {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value()+"", msg);
}
public static Result error(String code, String msg) {
Result r = new Result();
r.setCode(code);
r.setMessage(msg);
return r;
}
}
15、controller层
@Controllerpublic class LoginController {
@Autowired
UserService userService;
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
String credentials = "admin111";
int hashIterations = 2;
Object obj = new SimpleHash(hashAlgorithmName, credentials, null, hashIterations);
System.out.println(obj);
}
@GetMapping(value = {"/","/tologin"})
public String index(){
return "login";
}
@GetMapping(value = "/403")
public String toNoAuth(){
return "/403";
}
@GetMapping(value = "/success")
public String toSuccess(){
return "/success";
}
/**
* @Author:MuJiuTian
* @Date:2019/11/4 上午9:53
* @Description:简单的注册逻辑(只是针对shiro接下来的测试使用)
*/
@GetMapping(value = "/regist")
@ResponseBody
public Result regist(String username, String password) {
// 1、注册的账号、密码判断
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return Result.error("username or pwd is avaliable null");
}
// 2、随机生成salt[设置长度为6,false为包含数字和字母]
String salt = CommonUtil.getRandStr(6,false);
String uid = new SimpleHash("MD5",password,salt,2).toString();
// 3、符合要求,注册用户,将数据存储到数据库
userService.regist(username,salt,uid);
return Result.success(uid);
}
@GetMapping(value = "/login")
@ResponseBody
public Result login(@RequestParam Map<String, Object> map) {
String userName = map.get("username").toString();
String password = map.get("password").toString();
Subject subject = SecurityUtils.getSubject();
// 认证前提前准备token
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try{
subject.login(token);
} catch (Exception e){
return Result.error("error");
}
return Result.success("success");
}
@RequiresPermissions("test_add
")
@GetMapping(value = "/auth")
@ResponseBody
public Result auth(String username){
return Result.success(username);
}
}
16、我们来测试吧
把刚刚我们sql数据插进去我们就直接登陆就好了,如果没有,那么我们自由注册也行,如下 注册成功之后页面没有跳转,那么接下来我们就使用我们刚刚的账号进行登陆,登陆成功之后
然后我们随便输入,主要是为了测试权限测试用,那么什么是权限测试,就是
@RequiresPermissions("test_wee")
加上这个注解之后,如果前端页面没有这个对应权限的话就不能访问该接口,当然如果success.html没有下面注解,
<shiro:hasPermission name="test_add"></shiro:hasPermission>
那么他就会直接跳转到ShiroRealm.java中的doGetAuthorizationInfo方法中查询该登陆的用户的权限,如果查出来的权限依然和接口上@RequiresPermissions("test_wee")不一样,那么依然不能访问。
如下查出来对应的权限,那么就可以访问该接口,因为都有test_add的权限。
最终点击之后成功图,输入什么就输出什么,表示已经成功访问该接口。
所以,现在所有的后台管理系统都是按照这个逻辑来,包括超级管理员、系统管理员等...只有部分权力或者所有权利等。
17、相关Shiro博客
Shiro盐值密码的加密方式
以上是 崛起于Springboot2.X之Shiro企业开发实践(54) 的全部内容, 来源链接: utcz.com/z/510453.html