文档章节

崛起于Springboot2.X + Shiro融会贯通(54)

木九天
 木九天
发布于 11/05 22:44
字数 4082
阅读 303
收藏 6

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

《SpringBoot2.X心法总纲》

      目前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;

    @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盐值密码的加密方式

© 著作权归作者所有

木九天

木九天

粉丝 218
博文 270
码字总数 201472
作品 0
海淀
程序员
私信 提问
加载中

评论(5)

yanhl
yanhl
源码呢
木九天
木九天 博主
源码都在上面呢,一点也不差了。
yanhl
yanhl
直接下载例子跑起来最好了
木九天
木九天 博主
刚上传的
崛起于Springboot2.X + Activiti5.22(42)

《SpringBoot2.X心法总纲》 (本篇博客已于2019-08-28优化更新) 声明:该博客主要是Springboot1.X和Springboot2.X集成Activiti5.22版本,并说一下两个版本的搭建不同的地方 技术:Springboo...

木九天
2018/12/17
2.6K
2
崛起于Springboot2.X + 100秒jar部署(19)

《SpringBoot2.X心法总纲》 序言:和打包war方式不同,如果你看过我上一篇:SpringBoot2.X打包war,jar会更简单。 1、pom.xml确定是jar <packaging>jar</packaging> 2、IDEA右侧,点击Maven...

木九天
2018/07/18
559
0
崛起于Springboot2.X + Actuator监控(59)

《SpringBoot2.X心法总纲》 提前声明:这篇博客讲的内容没有看点,写它是为了后面的Springcloud相关技术做铺垫。 1、pom依赖 <dependency> </dependency> 2、application.properties server...

木九天
11/26
80
0
崛起于Springboot2.X + freemaker(24)

《SpringBoot2.X心法总纲》 (本篇博客已于2019-08-28优化更新) 1、pom <dependency> </dependency> 2、application.yml spring:freemarker: profiles: 如果你使用的是ftl后缀,那么更改suf......

木九天
2018/07/19
714
0
崛起于Springboot2.X + ip2region(31)

《SpringBoot2.X心法总纲》 ip2region:可以根据他获取一个具体ip的信息,国家、具体地址、网络服务商 1、添加依赖 <dependency> </dependency> 2、工具类 import org.lionsoul.ip2region.Da...

木九天
2018/08/03
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

sed -i linux 批量替换命令

批量替换 /usr/local/rocketmq/conf 目录下 的 xml 里头的 ${user.home} 替换为 /usr/local/rocketmq # mkdir -p /usr/local/rocketmq/logs# cd /usr/local/rocketmq/conf && sed -i 's#${......

jxlgzwh
27分钟前
4
0
如何在嵌入式CSS中编写a:hover?

我有一种情况,我必须编写内联CSS代码,并且我想在锚点上应用悬停样式。 如何在HTML样式属性内的CSS中使用a:hover ? 例如,您不能在HTML电子邮件中可靠地使用CSS类。 #1楼 简短的答案:您不...

技术盛宴
35分钟前
4
0
一些常用工具下载

golang: https://dl.google.com/go/go1.13.5.window-amd64.zip https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz 更换版本号可以下载其他版本。...

bobby2006
41分钟前
4
0
centos使用yum安装或者更新时总是提示被PackageKit占用

centos使用yum安装或者更新时总是提示被PackageKit占用 使用yum安装或更新软件时总是提示yum被PackageKit锁定占用 Existing lock /var/run/yum.pid: another copy is running as pid 13090. ...

流麦士
47分钟前
4
0
使用CSS内容添加HTML实体

如何使用CSS content属性添加html实体? 使用这样的东西只打印  到屏幕而不是不间断的空间: .breadcrumbs a:before { content: ' ';} #1楼 更新 :PointedEars提到正确的立...

javail
50分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部