Shiro教程,整合SpringBoot项目实战(笔记)

1.shiro
1.1什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
1.2什么是身份认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡
1.3什么是授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的

2.什么是shiro
Shiro是apache旗下一一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一 个通用的安全认证框架。

在这里插入图片描述
3.上述shiro的模块内容的具体含义(略)
4.shiro中的认证
4.1认证:
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
4.2 shiro中认证的关键对象
●Subject: 主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
●Principal: 身份信息
是主体(subject) 进行身份认证的标识,标识必须具有唯一性, 如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有-个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
4.3认证流程图
在这里插入图片描述
5.第一个shiroDemo
1.创建一个maven的项目
引入pom.xml

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

2.shiro.ini(用户配置文件)模拟从数据库中拿用户信息

[users]
hucong=123
zs=123456
ls=666

3.编写测试shiroTest

public class shiroTest {
    public static void main(String[] args) {
        //1.获得securityFactory工厂
        IniSecurityManagerFactory managerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2. 得到SecurityManager实例 并绑定给SecurityUtils
        SecurityManager securityManager = managerFactory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("hucong", "123");
        //4、身份认证
        try {
            System.out.println("是否授权:"+subject.isAuthenticated());
            subject.login(token);
            System.out.println("用户信息: "+token.getPrincipal());
            System.out.println("是否授权:"+subject.isAuthenticated());
        }catch (ArithmeticException e){
        }
    }
}

5.验证是否测试通过
在这里插入图片描述
6.shiro中自定义Realm的实现
1.创建自定义的CustomerRealm

public class CustomerRealm extends AuthorizingRealm {
   //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //在token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);

        //模拟数据库Mybatis 或者 jdbc中查询相关数据
        if ("hucong".equals(principal)){
            //参数1:数据库中的用户名,参数2:返回数据库中正确密码,参数3:提供当前realm的名字,this.getName()
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("hucong", "123456", this.getName());
            return info;
        }
        return null;
    }
}

2.测试testCustomerAuthenticator

public class testCustomerAuthenticator {
    public static void main(String[] args) {
        //创建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        //设置自定义realm
        defaultSecurityManager.setRealm(new CustomerRealm());
        //将安全工具类设置为安全类
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();

        //创建token
        UsernamePasswordToken token = new UsernamePasswordToken("hucong", "123456");

        //认证
        try {
            subject.login(token);
        }catch (ArithmeticException e){

        }
    }
}

3.验证结果
在这里插入图片描述
7.MD5和salt简介和执行流程
在这里插入图片描述
1.创建一个模拟登陆test

public class TestCustomerMD5RealmAuthentiactor {
    public static void main(String[] args) {
        //创建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        //设置自定义Realm
        CustomerMD5Realm realm = new CustomerMD5Realm();
        //设置凭证匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        //注入realm
        defaultSecurityManager.setRealm(realm);

        //将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //通过安全工具获取subject
        Subject subject = SecurityUtils.getSubject();

        //创建token认证
        UsernamePasswordToken token = new UsernamePasswordToken("hucong", "123456");

        //认证
        try {
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }

    }
}

2.模拟加盐加密

public class CustomerMD5Realm extends AuthorizingRealm {

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       //在tonken中获取用户名
        String principal = (String) token.getPrincipal();


//        //模拟数据库中校验
        if ("hucong".equals(principal)){

            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,
                    "89271947374d99d000defe5e2bbae8ca",
                    ByteSource.Util.bytes("op*xx"),
                    this.getName());
            return info;
        }


        return null;
    }
}

输出结果:
在这里插入图片描述
8.shiro中的授权
1.授权:
授权,即访问控制,控制谁能访问哪些资源,主体进行身份认证后需要分配权限方可访问系统资源,对于某些资源来说没有权限是玩无法访问的
2.关键对象

授权可简单理解为who对what(which)进行How操作: .
Who,即主体(Subject), 主体 需要访问系统中的资源。
What,即资源(Resource) ,如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
How,权限/许可(Permission) ,规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
3.授权流程
在这里插入图片描述
4.授权方式
●基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control) 是以角色为中心进行访问控制
●基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制

5.权限字符串
权限字符串的规则是:资源标识符: 操作: 资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“.”是资源/操作/实例的分割符,权限字符串也可以使用通配符。
例子:
●用户创建权限: user:create, 或user:create:

●用户修改实例001的权限: user:update:001
●用户实例001的所有权限: user:*: 001

6.shiro授权的编程实现
1.进行授权操作

public class CustomerMD5Realm extends AuthorizingRealm {
    //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String principal = (String) principals.getPrimaryPrincipal();
        System.out.println("身份信息:"+principal);
        //根据身份信息,用户名,获取当前角色信息,权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole("admin");
        info.addRole("user");

        //将数据库中查询的权限信息赋值权限
        info.addStringPermission("user:*:01");

        return info;
    }
    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       //在tonken中获取用户名
        String principal = (String) token.getPrincipal();


        //模拟数据库中校验
        if ("hucong".equals(principal)){

            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,
                    "8b74a0b41243f5817571d0e8b3a3bb0b",
                    ByteSource.Util.bytes("op*xx"),

                    this.getName());
            return info;
        }


        return null;
    }
}

public class TestCustomerMD5RealmAuthentiactor {
    public static void main(String[] args) {
        //创建securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        //设置自定义Realm
        CustomerMD5Realm realm = new CustomerMD5Realm();
        //设置凭证匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //设置使用算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //设置散列次数
        hashedCredentialsMatcher.setHashIterations(1200);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        //设置realm
        defaultSecurityManager.setRealm(realm);


        //将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //通过安全工具获取subject
        Subject subject = SecurityUtils.getSubject();

        //创建token认证
        UsernamePasswordToken token = new UsernamePasswordToken("hucong", "123456");

        //认证
        try {
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }

        if(subject.isAuthenticated()) {
            //基于角色权限管理
            boolean admin = subject.hasRole("admin");
            System.out.println(admin);
            System.out.println("======================");
            System.out.println(subject.hasRoles(Arrays.asList("user", "admin")));
            System.out.println("========================");


            //基于权限管理
            System.out.println(subject.isPermitted("user:*:01"));

        }
    }
}

9.shiro整合springboot(重点)
在这里插入图片描述
版本一:shiro+springboot+jsp+mysql+mybatis(先暂时用假数据模拟数据库)进行整合,因为shiro对jsp的支持是比较完美的,所以先对jsp进行整合,之后在做HTML与shiro的整合
编写如下两个页面index.jsp和login.jsp将这两个页面丢在webapp里,项目结构如图:
在这里插入图片描述
index.jsp

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

        hello world
        欢迎你:${user}
        <a href="${pageContext.request.contextPath}/user/logout">退出</a>
</body>
</html>

login.jsp

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
            <h3>用户登录</h3>
           <form action="${pageContext.request.contextPath}/user/login" method="post">
               用户登录: <input type="text" name="username"><br/>
               密码    :<input type="password" name="password"> <br/>
                <input type="submit" value="登录">
           </form>
</body>
</html>

引入相关依赖pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

<!--       引入jsp依赖包-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>9.0.35</version>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

<!--    集成shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>

编写application.properties(暂时这些,后面再加mysql相关的 起别名什么的)

server.port=8989
server.servlet.context-path=/shiro
spring.application.name=shiro

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

运行项目:
在这里插入图片描述
在这里插入图片描述
可以看到,在为设置shiro进行拦截时,两个页面都可以通过url进行访问
接下来实现shiro拦截(认证阶段)
编写一个ShiroConfig配置类放在config包下,(项目结构在上图,这里接不写出来)


@Configuration
public class ShiroConfig {
    //1.创建shiroFilter
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultSecurityManager defaultSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultSecurityManager);
        //配置系统公共资源
        Map<String,String> map = new HashMap<>();
        //anon设置为公共资源,放行资源在下面
        map.put("/user/login","anon");
        map.put("/**","authc");//authc 是需要授权认证的

        //默认认证界面路径
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;

    }

    //2.创建安全管理工具
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    //3.创建自定义realm
    @Bean
    public Realm getRealm(){
        CustomerRealms customerRealms = new CustomerRealms();

        return customerRealms;

    }

}

在自定义Realms


//自定义Realm
public class CustomerRealms extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户对象
        String principal = (String) token.getPrincipal();
        System.out.println(principal);

        //模拟数据库Mybatis 或者 jdbc中查询相关数据,这里模拟假数据进行登录
        if ("hucong".equals(principal)){
            //参数1:数据库中的用户名,参数2:返回数据库中正确密码,参数3:提供当前realm的名字,this.getName()
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("hucong", "123456", this.getName());
            return info;
        }
        return null;
    }
}

编写Controller进行测试


@Controller
@RequestMapping("user")
public class UserController {
    //创建登录方法
    @RequestMapping("login")
    public String login(String username,String password, HttpSession session){
        //获取主体对象
        Subject subject = SecurityUtils.getSubject();
        //拿到token
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        //将token进行认证
        try {
            //登录成功
            subject.login(token);
            System.out.println("用户登录成功");
            String user = (String) subject.getPrincipal();
            System.out.println("这是登录成功的用户名:"+user);
            session.setAttribute("user",user);

            return "redirect:/index.jsp";
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("未知的用户名!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误!!");
        }


        return "redirect:/login.jsp";
    }
    //退出
    @RequestMapping("logout")
    public String logout(){

        //获取主体对象
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect: /login.jsp";
    }

}

我们可以看到在后面输入什么都会跳转到login.jsp下,说明拦截成功
在这里插入图片描述
在这里插入图片描述
登陆跳转
在这里插入图片描述
暂时集成Shiro成功,接下来把数据库加上,先实现用户注册功能
创建名字为shiro的数据库再建一张user表:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `salt` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

创建实体类

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String id;
    private String username;
    private String password;
    private String salt;
}

创建UserMapper

@Mapper
public interface UserMapper {
    //注册方法
    void save(User user);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hu.springboot_jsp_shiro.mapper.UserMapper">

<!--    添加注册的sql-->
    <insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values (#{id},#{username},#{password},#{salt})
    </insert>
</mapper>

然后在实现serviceImpl

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;

    @Override
    public void register(User user) {
        //调用mapper
        //1.生成随机盐。这里需要一个utils来生成随机盐
        String salt = SaltUtils.getSalt(8);
        //2.将随机盐保存到数据库
        user.setSalt(salt);
        //3.将明码进行MD5+salt+hash散列//参数是source:密码  salt:随机盐,hash:散列次
        Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
        //将生成的加密密码存入数据库
        user.setPassword(md5Hash.toHex());
        //所有保存到数据库
        userMapper.save(user);
    }
}

这里我们需要随机生成盐,所以编写一个Utils包下实现SaltUtils随机生成盐

public class SaltUtils {
    /**
     * 生成随机盐salt的静态方法
     * @param n
     * @return
     */
    public static String getSalt(int n){
    //自定义一个数组
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*".toCharArray();
       StringBuilder sb = new StringBuilder();
        for (int i = 0;i<n;i++){
        //截取随机长度的字符串
            char achar = chars[new Random().nextInt(chars.length)];
            //保存在sb里
            sb.append(achar);
        }
        //返回盐
        return sb.toString();

    }
}

然后在controller中编写用户注册


/**
 * 用户注册
 * @param user
 * @return
 */
@RequestMapping("register")
public String register(User user){
    try {
        userService.register(user);
        System.out.println("注册成功");
        //返回登录页面
        return "redirect:/login.jsp";
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("注册失败!!!");
        return "redirect:/register.jsp";
    }
}

编写注册jsp页面

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
            <h3>用户注册</h3>
           <form action="${pageContext.request.contextPath}/user/register" method="post">
               用户登录: <input type="text" name="username"><br/>
               密码    :<input type="password" name="password"> <br/>
                <input type="submit" value="注册">
           </form>
</body>
</html>

注意这里要方行该页面,需在ShiroConfig中配置系统公共资源下方添加如下两行代码

map.put("/user/register","anon");//anon 设置为公共资源,方行
map.put("/register.jsp","anon");//anon 设置为公共资源,方行

注册功能完成,然后进行测试
在这里插入图片描述
查看数据库
在这里插入图片描述
注册功能完成。

接下来进行**用户认证(也就是真实数据库中的登录功能)**前面的登录方法我大致了解到登录是我们从前台获取用户名然后通过用户名去数据库中查找,然后比较密码(比较密码这一步是shiro帮我们做了),我们只需要拿到用户名查询数据库之后得到token,然后对输入的密码设置凭证匹配器,设置散列次数即可,废话不多说,上代码
1.我们首先需要一个通过用户名去查询数据库的方法(mapper下)

   //通过用户名查找用户
    User findByUserName(String username);
}

2.mapper.xml


<!--添加一个通过用户名去查找用户信息-->
    <select id="findByUserName" parameterType="String" resultType="User">
        select id, username,password,salt from t_user where
        username = #{username}
    </select>

3.serviveImpl

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
//上面注册的代码省略,详情看上面
.........
//
@Override
public User findByUserName(String username) {
    return userMapper.findByUserName(username);
}

4.由于下面会通过getBean的方式去获取对象,所以我这里创建一个ApplicationContextUtils类

@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    //根据bean名字获取工厂中指定对象
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

5.然后修改CustomerRealms的认证方法

//认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户对象
        String principal = (String) token.getPrincipal();
        System.out.println(principal);

        //模拟数据库Mybatis 或者 jdbc中查询相关数据,这里模拟假数据进行登录
        //这里是通过getBean的方式去从数据库中拿数据
        //在工厂中获取service对象
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        System.out.println(userService);
        User user = userService.findByUserName(principal);
        if (!ObjectUtils.isEmpty(user)){
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
        }

        return null;
    }
}

6.更改shiroConfig


//3.创建自定义realm
@Bean
public Realm getRealm(){
    CustomerRealms customerRealms = new CustomerRealms();
    //设置凭证匹配器
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
   //设置md5
    hashedCredentialsMatcher.setHashAlgorithmName("md5");
    //散列次数
    hashedCredentialsMatcher.setHashIterations(1024);
    customerRealms.setCredentialsMatcher(hashedCredentialsMatcher);
    return customerRealms;

}

测试
在这里插入图片描述

在这里插入图片描述
完成授权功能模块授权模块其实非常简单
主要是在自定义Realm下重写doGetAuthorizationInfo方法就可以了(里面的数据写死了的,下面会介绍从数据库中读取)

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String principal = (String) principals.getPrimaryPrincipal();
    System.out.println("授权中的用户名:"+principal);
    //根据主身份信息获取角色和权限信息
    //当然这里写死了,肯定是不对的,后面会介绍从数据库中获取权限 
    if ("hucong".equals(principal)){
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRole("user");

        simpleAuthorizationInfo.addStringPermission("user:find:*");
        simpleAuthorizationInfo.addStringPermission("user:update:*");
        return simpleAuthorizationInfo;
    }

    return null;
}

然后前端进行校验
在index.jsp中加上
<%@taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
jsp下支持shiro的标签

<body>
        hello world
        欢迎你:${user}
        <a href="${pageContext.request.contextPath}/user/logout">退出</a><br/>
        <shiro:hasAnyRoles name="user, admin">
            <li><a href="">用户管理</a><br/>
                <ul>
                    <shiro:hasPermission name="user:add:*">
                        <a href="">添加</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:deltet:*">
                        <a href="">删除</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:update:*">
                        <a href="">编辑</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:find:*">
                    <a href="">查询</a>
                    </shiro:hasPermission>
                </ul>
            </li>
        </shiro:hasAnyRoles>
        <shiro:hasRole name="admin">
            <a href="">商品管理</a><br/>
            <a href="">订单管理</a><br/>
            <a href="">物流管理</a>
        </shiro:hasRole>
</body>

未对用户授权
在这里插入图片描述
对用户授权后
在这里插入图片描述
接下来从数据库中读出用户角色权限
用户角色权限数据表关系
在这里插入图片描述
表sql:

-- ----------------------------
-- Table structure for t_pres
-- ----------------------------
DROP TABLE IF EXISTS `t_prems`;
CREATE TABLE `t_pres`  (
  `id` int(6) NOT NULL,
  `name` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_prems` VALUES (1, 'user:*:*', NULL);
INSERT INTO `t_prems` VALUES (2, 'product:*:01', NULL);
INSERT INTO `t_prems` VALUES (3, 'order:*:*', NULL);
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `name` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- -----------------------------
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user');
INSERT INTO `t_role` VALUES (3, 'product');
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms`  (
  `id` int(6) NOT NULL,
  `roleid` int(6) NULL DEFAULT NULL,
  `permsid` int(6) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_role_perms` VALUES (1, 1, 1);
INSERT INTO `t_role_perms` VALUES (2, 1, 2);
INSERT INTO `t_role_perms` VALUES (3, 2, 1);
INSERT INTO `t_role_perms` VALUES (4, 3, 2);
INSERT INTO `t_role_perms` VALUES (5, 1, 3);

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `salt` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_user` VALUES (1, 'hucong', '78b5d50913b429cf6b2468735f5b9aa9', 'L^C*Ikd@');
INSERT INTO `t_user` VALUES (2, 'zhangsan', '5a6d188e5bc9020147fff7a0fcddf4b3', 'AHbSIGe!');

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` int(6) NOT NULL,
  `userid` int(6) NULL DEFAULT NULL,
  `roleid` int(6) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_user_role` VALUES (1, 1, 1);
INSERT INTO `t_user_role` VALUES (2, 2, 2);
INSERT INTO `t_user_role` VALUES (3, 2, 3);

接下来
在这里插入图片描述
那么在userMapper中创建

  //根据用户名查询所有角色
    User findRolesByUserName(String username);
}

userMapper.xml中


    <resultMap id="userMap" type="User">
        <id column="uid" property="id" />
        <result column="username" property="username" />
<!--        角色信息-->
        <collection property="roles" javaType="list" ofType="Role">
            <id column="id" property="id" />
            <result column="rname" property="name" />
        </collection>
    </resultMap>

<!--    添加一个根据用户名查询所有角色-->
    <select id="findRolesByUserName" parameterType="String" resultMap="userMap">
        SELECT
            u.id uid,
            u.username,
            r.id,
            r.`name` rname
        FROM
            t_user u
            LEFT JOIN t_user_role ur ON u.id = ur.userid
            LEFT JOIN t_role r ON ur.roleid = r.id
        WHERE
            u.username = #{username}
    </select>

service与serviceImpl中方法与mapper一致,这里就省略了
然后编写自定义Realm

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String principal = (String) principals.getPrimaryPrincipal();
    System.out.println("授权中的用户名:"+principal);
    //根据主身份信息获取角色和权限信息//从数据库中去拿
    UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
    User user = userService.findRolesByUserName(principal);
    //授权角色信息
    if (!CollectionUtils.isEmpty(user.getRoles())){
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        user.getRoles().forEach(role -> {
            simpleAuthorizationInfo.addRole(role.getName());
        });
        return simpleAuthorizationInfo;
    }

    return null;
}

运行结果在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
角色信息授权完成。

权限数据信息在数据库中的获取
在实体类Role.java中定义

//定义一个权限的集合,存权限信息
private List<Perms> perms;

userMapper下


//根据角色id查询权限集合
List<Perms> findPermsByRoleId(String id);

mapper.xml

<select id="findPermsByRoleId" resultType="Perms" parameterType="String">
    SELECT
        p.id,
        p.`name`,
        p.url,
        r.`name`
    FROM
        t_role r
        LEFT JOIN t_role_perms rp ON r.id = rp.roleid
        LEFT JOIN t_prems p ON p.id = rp.permsid
    WHERE
        r.id = #{id}
</select>

service与serviceImpl这里省略 ,就是接口的调用,方法是一样的
然后在CustomerRealms下添加这几行代码即可
在这里插入图片描述
index.jsp

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro"  uri="http://shiro.apache.org/tags" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

        hello world
        欢迎你:${user}
        <a href="${pageContext.request.contextPath}/user/logout">退出</a><br/>
        <shiro:hasAnyRoles name="user, admin">
            <li><a href="">用户管理</a><br/>
                <ul>
                    <shiro:hasPermission name="user:add:*">
                        <a href="">添加</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:deltet:*">
                        <a href="">删除</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:update:*">
                        <a href="">编辑</a>
                    </shiro:hasPermission>
                    <shiro:hasPermission name="user:find:*">
                    <a href="">查询</a>
                    </shiro:hasPermission>
                </ul>

            </li>

        </shiro:hasAnyRoles>
        <shiro:hasRole name="admin">
            <a href="">商品管理</a><br/>
            <a href="">订单管理</a><br/>
            <a href="">物流管理</a>
        </shiro:hasRole>

</body>
</html>

运行截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这 里功能基本实现

10使用CacheManager优化

但是需要优化,因为每次都是去数据库中拿取用户信息、权限信息,刷新一下页面,就会从数据库中去查询一次,所以这里需要优化一下

1.Cache作用
●Cache 缓存:计算机内存中一段数据
●作用:用来减轻DB的访问压力,从而提高系统的查询效率

2.使用shiro中默认EhCache实现缓存

1)引入依赖	
<!--        shiro与ehcahe-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.3</version>
        </dependency>
2)在ShiroConfig的自定义Realm中加上
//3.创建自定义realm
@Bean
public Realm getRealm(){
 ........部分代码省略...........
    //开启缓存管理
    customerRealms.setCacheManager(new EhCacheManager());
    customerRealms.setCachingEnabled(true);//全局缓存
    customerRealms.setAuthenticationCachingEnabled(true);//开启认证缓存
    customerRealms.setAuthenticationCacheName("authenticationCache");
    customerRealms.setAuthorizationCachingEnabled(true);//开启授权缓存
    customerRealms.setAuthorizationCacheName("authorizationCache");
    return customerRealms;

}

这种方式的缺点是如果服务器宕机或者从程序重新启动,就会重新去数据库中访问,感觉还是不太友好

11.使用Redis优化
1)引入依赖

<!--        引入redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.0.RELEASE</version>
        </dependency>

2)编写yml或者properties

#Redis相关配置
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0

3)启动Redis
在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值