SpringBoot--集成Shiro

  shiro主要有用户认证和用户授权两个功能

一、用户认证

1、导入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

2、新增测试页面

  新增测试页面:

    login.html(登陆页面)、index.html(登陆成功页面)、error.html (无权限页面)、add.html(添加页面)、update.html(修改页面)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
<hr/>
<h3 th:text="${msg}" style="color: red"></h3>
<form method="post" action="login">
    用户名 :<input type="text" name="username"><br>
    密 码 :<input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>测试Thymeleaf</title>
</head>
<body>
<h3 th:text="${test}"></h3>
<hr/>
进入用户添加页面:<a href="add">用户添加</a>
<br>
进入用户修改页面:<a href="update">用户修改</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>无权限</title>
</head>
<body>
<h1>无权限页面</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户添加页面</title>
</head>
<body>
<h1>用户添加页面</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户修改页面</title>
</head>
<body>
<h1>用户修改页面</h1>
</body>
</html>

3、新增控制类

  新增控制类,包含登陆方法(login)、跳转登陆页面方法(toLogin)、添加用户页面(add)、修改用户页面(update)、不被拦截的方法(hello)

  其中需要注意的就是登陆方法(login),所有登录校验都交给shiro框架处理。

package com.example.demo.controller;

import com.example.demo.resource.ShiroConfig;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class LoginController {

    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @ResponseBody
    @GetMapping(value = "/hello")
    public String hello(){
        logger.info("不登陆也能访问");
        return "hello不登陆也能访问";
    }

    @RequestMapping(value = "/index")
    public String testThymeleaf(Model model) {
        //把数据存入model
        model.addAttribute("test", "测试Thymeleaf");
        //返回test.html
        return "/index";
    }

    @RequestMapping(value = "/add")
    public String add(Model model) {
        //把数据存入model
        model.addAttribute("test", "添加用户页面");
        //返回test.html
        return "/user/add";
    }

    @RequestMapping(value = "/update")
    public String update(Model model) {
        //把数据存入model
        model.addAttribute("test", "修改用户页面");
        //返回test.html
        return "/user/update";
    }

    @RequestMapping(value = "/toLogin")
    public String toLogin(){
        logger.info("跳转登陆页面");
        return "/login";
    }



    @PostMapping(value = "login")
    public String login(String username,String password, Model model){
        logger.info("登陆用户名【{}】,密码【{}】",username,password);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try{
            subject.login(token);
        }catch (UnknownAccountException e){
            logger.error("对用户[{}]进行登录验证,验证未通过,用户不存在", username);
            token.clear();
            return "login";
        }catch (LockedAccountException lae) {
            logger.error("对用户[{}]进行登录验证,验证未通过,账户已锁定", username);
            token.clear();
            return "login";
        } catch (ExcessiveAttemptsException e) {
            logger.error("对用户[{}]进行登录验证,验证未通过,错误次数过多", username);
            token.clear();
            return "login";
        } catch (AuthenticationException e) {
            logger.error("对用户[{}]进行登录验证,验证未通过,堆栈轨迹如下", username, e);
            token.clear();
            return "login";
        }
        return "/index";
    }


}

4、(重点)创建自己的认证类

扫描二维码关注公众号,回复: 7690309 查看本文章

  认证类需要继承AuthorizingRealm类,并重写认证和授权)两个方法。

    认证方法(doGetAuthenticationInfo):该方法在登陆时调用,用来做登录校验

    授权方法(doGetAuthorizationInfo):该方法在需要验证权限时调用,用来判断是否拥有权限

  此处先校验登陆问题,因此先只处理认证方法,授权问题详见第二点。在认证方法中,查询了数据库与前台传入的用户名、密码进行校验,此处的查询数据库的一系列文件(service、serviceImpl、mapper、dao、数据库建表等)不再演示。

package com.example.demo.utils;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import java.util.*;

/**
 * 认证
 */
@Configuration
public class AuthRealm extends AuthorizingRealm {

    Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private UserService userService;

    /**
     * 只有需要验证权限时才会调用, 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.在配有缓存的情况下,只加载一次.
     * 如果需要动态权限,但是又不想每次去数据库校验,可以存在ehcache中.自行完善
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("用户授权");
        return null;
    }

    /**
     * 认证回调函数,登录时调用
     * 首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;
     * 如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,
     * 交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,
     * 如果不匹配将抛出密码错误异常IncorrectCredentialsException;
     * 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;
     * 在组装SimpleAuthenticationInfo信息时, 需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),
     * CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("用户认证");
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        User user = userService.selectOneByUsername(usernamePasswordToken.getUsername());
        //2、判断用户名称是否存在
        if (null == user || !user.getUsername().equals(usernamePasswordToken.getUsername())) {
            //用户名称不存在,Shiro底层会抛出UnknowAccountException
            return null;
        }
        //3、判断密码是否正确
        return new SimpleAuthenticationInfo((String)token.getPrincipal(), user.getPassword(), user.getUsername());
    }
}

5、(重点)配置shiro配置类

  shiro配置类里面主要有三点:

    (1)获取自定义的认证类

    (2)获取带有认证类(自定义的认证类)的安全管理器(SecurityManager)

    (3)获取带有安全管理器的过滤器

  关于过滤器,主要时配置拦截规则,以及各种情况跳转的页面信息。其中要注意两点,一定要把resource下的所有文件置为无需拦截,否则就会发生虽然方法没有被拦截,但是由于页面被拦截而跳转登陆页面的情况;还有一个就是filterMap的最后一定要要把/**职位需要验证。

package com.example.demo.resource;

import com.example.demo.utils.AuthRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Bean
    public AuthRealm getAuthRealm(){
        return new AuthRealm();
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(authRealm);
        return defaultWebSecurityManager;
    }

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
/*        内置Shiro过滤器实现相关拦截功能
                常用的过滤器有:
                    anon  : 无需认证(登录)可以访问
                    authc : 必须认证才能访问
                    user  : 如果使用rememberMe的功能可以直接访问
                    perms : 该资源必须得到资源访问权限才可以使用
                    role  : 该资源必须得到角色授权才可以使用*/
        Map<String,String> filterMap = new HashMap<>();
        filterMap.put("/resource/**","anon");
        filterMap.put("/install","anon");
        filterMap.put("/hello","anon");
        filterMap.put("/login","anon");
        //filterMap.put("/add","perms[user:add]");
        filterMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
     //未登录跳转登陆页面 shiroFilterFactoryBean.setLoginUrl(
"/toLogin");
     //认证成功跳转URL shiroFilterFactoryBean.setSuccessUrl(
"/test");
     //登录后无权限URL shiroFilterFactoryBean.setUnauthorizedUrl(
"/error"); return shiroFilterFactoryBean; } }

6、测试

  此处测试分为以下几点:

  

二、资源授权

  第一点中的第5步中关于shiro配置类的代码中有一行代码是注释掉的(filterMap.put("/add","[user:add]");),这个就是对于资源的授权,这一行的代码就是表明访问add时需要user:add权限,放开该行注释后,再点击添加和修改,可以看到用户修改可以正常跳转,但是新增用户就会跳转到我们设置的无权限页面。

      

  所以就需要对用户进行资源的授权。

1、导入依赖

        <!-- thymeleaf 扩展 shiro-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2、前端页面添加shiro标签

  此处引入了shiro标签xmlns:shiro="http://www.w3.org/1999/xhtml",然后在连接处使用了该标签 hiro:hasPermission

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>测试Thymeleaf</title>
</head>
<body>
<h3 th:text="${test}"></h3>
<hr/>
<div shiro:hasPermission="user:add">
    进入用户添加页面:<a href="add">用户添加</a>
</div>
<br>
<div shiro:hasPermission="user:update">
    进入用户修改页面:<a href="update">用户修改</a>
</div>
</body>
</html>

3、修改AuthRealm类中的授权方法

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("用户授权");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
        String username = (String)subject.getPrincipal();
        User user = userService.selectOneByUsername(username);
        simpleAuthorizationInfo.addStringPermission(user.getPerms());
        return simpleAuthorizationInfo;
    }

4、数据库调整

  为admin用户添加新增权限,为lcl用户添加修改权限

  

5、测试

  使用admin用户登陆,则只能看到新增按钮,使用lcl用户登陆,则只能看到update按钮。

                      

  关于springBoot整合Shiro就整合完毕了,其中有几点不合理的地方,为了演示整合,就没有写的那么详细,比如说,一般情况下,会使用用户、角色、权限三张表来做权限处理、会使用缓存来对用户信息进行缓存等。

猜你喜欢

转载自www.cnblogs.com/liconglong/p/11767009.html