崛起于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=8071

spring.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、实体类

@Data

public 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;

}

@Data

public 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;

}

@Data

public 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;

}

@Data

public class SysRolePermission {

// 编号

private Integer id;

// 角色id

private Integer roleId;

// 权限id

private Integer permissionId;

}

@Data

public 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层

@Mapper

public 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层

@Service

public 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层

@Controller

public 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

回到顶部