SpringBoot:Shiro的学习以及SpringBoot整合Shiro

SpringBoot:整合Shiro 

 

什么是Apache Shiro?

Apache Shiro是一个功能强大且易于使用的Java安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话管理解决方案。 

Shiro官网上有一个十分钟快速入门的教程:关于Apache Shiro的10分钟教程,并且从官网上下载Shiro以后,源码里有/samples/quickstart下也有一个Quickstart类,用于快速上手Shiro。

十分钟教程和Quickstart类都是Shiro给我们的上手指南,他的目的是让我们熟悉API和Shiro的概念。

Quickstart类通过机翻和自己的辣鸡翻译加上的注释

public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // 在类路径的根目录下使用shiro.ini文件,返回一个SecurityManager实例:
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //得到 securityManager 实例
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // 获取当前执行的用户
        Subject currentUser = SecurityUtils.getSubject();

        // 使用会话做一些事情(不需要web或EJB容器!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // 让我们登录当前用户,这样我们可以检查角色和权限:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // 在这里捕获更多的异常(可能是特定于您的应用程序的自定义异常)
            catch (AuthenticationException ae) {
                //以防一些为止的错误和意外
            }
        }

        //说出他们是谁:
        //打印它们的标识主体(在本例中为用户名):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        // 判断当前用户是否是schwartz
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        // 判断当前用户是否有权做些什么(细粒度)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //判断当前用户是否有权做些什么(细粒度,更详细的权限检查)
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //注销
        currentUser.logout();

        System.exit(0);
    }
}

总结出来Shiro的几个核心API

SecurityManager securityManager = factory.getInstance();  // 得到SecurityManager实例
Subject currentUser = SecurityUtils.getSubject();         // 获取当前执行的用户
currentUser.isAuthenticated()                             // 判断用户是否已登录
currentUser.hasRole("schwartz")                           // 判断用户的角色         
currentUser.isPermitted("lightsaber:wield")               // 判断用户是否有权做什么
currentUser.logout();                                     // 注销

Shiro架构(从外部来看)

一个最简单的Shiro应用,应用代码通过Subject来进行认证和授权,Subject会托付给SecurityManager来执行,我们需要给SecurityManager注入Realm,让SecurityManager能得到合法的用户及其权限进行判断。

å¨è¿éæå¥å¾çæè¿°

 

Shiro架构(从内部来看):

  • Subject:是任何可以与应用交互的用户
  • SecuurityManager:相当于SpringMVC中的DispatcherServlet,是Shiro的心脏,所有的具体交互都通过SecurityManager进行控制,它管理着所有的Subject,且负责进行认证,授权,会话及缓存管理
  • Authenticator:负责Subject认证,是一个拓展点,可以自定义实现;可以使用认证策略(Authercication Strategy),即什么情况下算认证通过
  • Authorizer:授权器,即访问控制器,用来决定主体是否有权进行相应的操作,即控制着用户能访问应用中的哪些功能;
  • Realm:可以有一个或者多个Realm,可以认为是安全实体的数据源,即用于获取安全实体,可以是JDBC实现,也可以是内存实现等,由用户提供。
  • SessionManager:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSe环境
  •  CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

Shiro的核心概念:Subject, SecuurityManager, Realms。

 

整合Shiro之前的准备工作

SpringBoot的环境前提:导入了Spring-web和Thymeleaf的依赖

准备工作:

  • HTML:编写首页,有一个add的跳转和一个update的跳转 ,对应的add页面和update页面。
  • Java:编写一个用于跳转的Controller

index.html: 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>

<a th:href="@{'/user/add'}">Add</a>
<a th:href="@{'/user/update'}">Update</a>
</body>
</html>

add.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Add</h1>
</body>
</html>

 update.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Update</h1>
</body>
</html>

Controller:

/**
 * @author Claw
 */
@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String test(Model model){
        model.addAttribute("msg","hello,Shiro");
        return  "index";
    }

    @RequestMapping("/user/add")
    public  String toAdd(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public  String toUpdate(){
        return "user/update";
    }

}

效果:点击Add和Update分别能进入Add和Update的页面

SpringBoot整合Shiro环境搭建

Shiro实现登录拦截

但是总所周知,既然是学安全的框架,这两个操作不是谁都可以的。

那么我们现在需要使Shiro对操作进行拦截,允许有权限的人进行操作,那么再执行了安全保护后,再点击Add和Update应该指向登录页面。

那么在MyController添加一个方法对登录页面的跳转,以及再编写一个login.html

    @RequestMapping("/toLogin")
    public  String toLogin(){
        return "login";
    }
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<form th:action="@{'/login'}">
  用户名: <input type="text" name="username"><br>
  密码: <input type="password" name="password">
  <input type="submit" value="提交">
</form>
<span th:text="${msg}" style="color: red"></span>
</body>
</html>

 导入Spring整合Shiro的依赖

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

SpringBoot集成Shiro只需要两个类 ,Shiro的配置类ShiroConfig以及自定义的Realm类 UserRealm。

ShiroConfig是对Shiro的一些配置,比如过滤的文件和权限,密码加密的算法等相关功能。  

UserRealm 是自定义的Realm类,继承了AuthorizingRealm,并且重写了doGetAuthorizationInfo(权限相关)和doGetAuthenticationInfo (身份认证)两个方法。

ShiroConfig方法的编写需要三个方法,再写这三个方法前,必须再强调Shiro的三个核心概念:

  1. Subject:代表当前正在执行操作的用户,只要对应用进行了操作,那么就可以认为他是那个Subject
  2. SecurityManager:SecurityManager是Shiro的核心,主要协调Shiro内部的各种安全组件,它需要注入Realm,让SecurityManager可以拿到合法的用户对其权限的判断。
  3. Realm:用户数据和Shiro数据交互的桥梁,比如需要用户认证,权限认证,都从Realm来读取数据。

ShiroConfig的三个方法,主要用来得到自定义的Realm对象,把得到的Realm对象注入给SecurityManager对象,再把SecurityManager对象给ShiroFilter。

ShiroFilter :是Shiro的过滤器,可以设置登录页面,权限不足跳转页面,具体某些页面的权限控制和身份认证。

它需要注入securityManager,我们通过ShiroFilterFactoryBean来得到Shiro的内置Filter。

我的建议从下往上来编写ShiroConfig,即:

获取Realm对象===>获取securityManager对象===》把Realm对象注入给securityManager对象===》把securityManager对象注入给ShiroFilter

为了得到Realm对象,首先来自定义UserRealm,继承AuthorizingRealm类并且重写父类的方法。

/**
 * 自定义的UserRealm
 * 继承AuthorizingRealm
 * 并重写 doGetAuthorizationInfo 和  doGetAuthenticationInfo
 * @author Claw
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 权限相关
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("获取授权信息");
        return null;
    }

    /**
     * 身份认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("获取认证信息");
        return null;
    }
}

我们定义好自定义的UserRealm后,在ShiroConfig中new一个就得到了。

那么如何得到SecuiryManager呢?SecuiryManager本身是一个接口,要得到它要看实现类,因为项目是Web工程,所以使用的是DefaultWebSecurityManager这个实现类,new一个DefaultWebSecurityManager得到它的对象,然后注入realm。

 那么ShiroFilter如何得到呢?通过ShiroFilterFactoryBean来得到Shiro的内置Filter,它的源码里说了它需要一个map,map里面的Key来存放需要被保护的URL,value是 内置过滤器的名字

常用的内置Filter:

anon    无需认证即可访问
authc   必须认证了才能访问
user    必须拥有记住我 功能才能用
perms   拥有对某个资源的权限

三个方法 1.得到Realm 2.得到Security,注入Realm 3.得到Security,注入给Fliter。

我们已经很明确思路了,现在来编写ShiroConfig,不要忘记@Configuation注解表明是配置类,也不要忘记给每个获取的对象的方法打上@Bean注解,我们需要让Spring容器来管理它们。

/**
 * @author Claw
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean bean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //注入SecurityManager
        bean.setSecurityManager(securityManager);
        //设置Fliter 需要一个map
        Map<String, String> filterMap= new HashMap();
        //Key 来定义哪些URL需要被限制,value是内置Flter过滤器名称,authc:必须认证了才能访问
        filterMap.put("/user/**","authc");
        bean.setFilterChainDefinitionMap(filterMap);
        //没有得到认证的请求会跳转到登录页进行授权
        bean.setLoginUrl("/toLogin");

        return  bean;
    }

    /**
     * 获取 ecurityManager
     * @param userRealm
     * @return securityManager
     */
    @Bean("securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(userRealm);
        return securityManager;
    }

    /**
     * 获取 Realm对象
     * @return userRealm
     */
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

到了这一步,我们已经对访问add页面和update的页面进行了权限的拦截,未授权的访问将会转到登录页面。

 Shiro实现用户认证

身份验证的三个流程:

1、收集用户身份/凭证,即如用户名/密码
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
3、创建自定义的 Realm 类,继承AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法
 

针对于第一点,username和password从登录的表单中获取,针对于第二点,在Login方法中调用Subject.login进行登录,针对第三点,我们已经自定义了UserRealm,认证的条件从realm中获取,因为目前还没有继承Mybatis,我们暂时可以把username和passowrd写死在Realm。
现在来编写处理用户登录的Login方法,在Shiro官方给出的快速上手的例子中,已经说明了如何编写,我们仿照写就ok。

        // 如果用户被认证了
        if (!currentUser.isAuthenticated()) {
            // 封装用户的登录数据
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            // 记住我功能
            token.setRememberMe(true);
            try {
                //调用登录方法
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // 在这里捕获更多的异常
            catch (AuthenticationException ae) {
                //以防一些为止的错误和意外
            }
        }

Login方法

/**
 * @author Claw
 */
@Controller
public class LoginController {

    @RequestMapping("/login")
    public String Login(String username,String password,Model model){
        //封装当前用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        //执行登录方法
        try{
            subject.login(token);
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }

        return "index";
    }
}

写完这个以后我们再去完善UserRealm的身份认证方法 doGetAuthenticationInfo(AuthenticationToken authenticationToken)

当我们得到表单提交过来的用户名和密码,将它封装到UsernamePasswordToken里,然后调用login方法,SecurityManager会收到AuthenticationToken的实例,把它发送到已定义的Realm里,执行必要的认证和检查。

在Login的方法中我们用了UsernamePasswordToken来封装用户名和密码,所以在Realm中,需要把参数AuthenticationToken 强转为UsernamePasswordToken,这是一个固定的写法。token定义好以后是全局都可以使用的。

用户名认证通过我们自己判断,密码是交给Shiro来做。

doGetAuthenticationInfo方法是要求返回一个AuthenticationInfo类型的对象,方法里直接new一个return回去就好,但是AuthenticationInfo是个接口,我们要返回它的实现类,这里用到的是SimpleAuthenticationInfo,它有三个参数,第一个参数是获取用户的认证,第二个是它要传递的密码,第三个是认证名。认证和认证名现在可以省略掉,我们需要把正确的密码传进去。

 UserRealm:

/**
 * 自定义的UserRealm
 * 继承AuthorizingRealm
 * 并重写 doGetAuthorizationInfo 和  doGetAuthenticationInfo
 * @author Claw
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 权限相关
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {


        System.out.println("获取授权信息");
        return null;
    }

    /**
     * 身份认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("获取认证信息");

        /**
         * 定义正确的用户名和密码,一般从数据库取,这里写死了.
         */
        String username = "admin";
        String password =  "12345";

        //我们用UsernamePasswordToken来封装的数据,把authenticationToken强转为UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //验证用户名,如果不正确,返回null,会在Login方法中捕获UnknownAccountException的异常
        if(!token.getUsername().equals(username)){
            return null;
        }
        //密码验证交给Shiro来验证
        return  new SimpleAuthenticationInfo("",password,"");
    }
}

写到这一步,我们已经完成了Shiro实现用户认证了。

登录成功的测试:

Shiro整合Mybatis

其实也就是SpringBoot整合mybatis,这里我同时用了durid的数据源。

步骤:

  1. 导入依赖
  2. 创建POJO实体类
  3. 创建Mapper以及配置映射文件
  4. 在配置文件中为mybatis做配置
  5. 创建Service接口以及实现类

导入依赖:

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

POJO:

/**
 * @author Claw
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
  private Integer id;
  private String username;
  private Date birthday;
  private String sex;
  private String address;
  private String pwd;
}

Mapper :

@Repository
@Mapper
public interface UserMapper {
    
    /**
     * 通过用户名查找用户
     * @param name
     * @return
     */
    User findUserByName(String name);

}

配置映射文件:

<?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.claw.mapper.UserMapper">
    <select id="findAll" resultType="user">
        select * from
        user
    </select>
    <select id="findUserByName" resultType="user" parameterType="String">
        select * from user where username=#{name}
    </select>
</mapper>

配置文件 yaml格式:

#thymeleaf缓存关闭 便于调试
spring:
  thymeleaf:
    cache: false
#配置数据库源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf-8
    username: root
    password: password
    type: com.alibaba.druid.pool.DruidDataSource
    #druid 数据源私有属性
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#mybatis的配置
mybatis:
  mapper-locations: com/claw/mapper/*.xml
  type-aliases-package: com.claw.pojo

Service接口:

/**
 * @author Claw
 */
public interface UserService {

    /**
     * 通过用户名查找用户
     * @param name
     * @return
     */
    User findUserByName(String name);
}

Service实现类:


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;
    @Override
    public User findUserByName(String name) {
        return userMapper.findUserByName(name);
    }
}

 

接下来在Realm中通过数据库获取数据,再进行用户名和密码的判断。

我的数据库本来没有密码这一列,我添加了一列,并且新插入了个admin的用户

    /**
     * 身份认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("获取认证信息");

        //我们用UsernamePasswordToken来封装的数据,把authenticationToken强转为UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //从数据库取用户
        User user = userService.findUserByName(token.getUsername());
        //如果用户名为空  返回null,会在Login方法中捕获UnknownAccountException的异常
        if (null == user){
           return null;
        }
        //密码验证交给Shiro来验证
        return  new SimpleAuthenticationInfo("",user.getPwd(),"");
    }

 Shiro请求授权

在Realm中只完成了认证,让登录的用户可以访问某些页面,但还未进行授权,也就是说登录的用户,当然也不能随便的进行操作。

所以还要完成授权,这一步需要在ShiroConfig中定义,在设置内置过滤器的地方添加一个 perms的过滤器:让/user/add这个访问路径需要有user:add的权限才可以进入。

        //授权: 授权才能操作
        filterMap.put("/user/add","perms[user:add");

当然我们现在未定义user:add,现在重启SpringBoot去访问Add页面,会显示未授权。

这个错误的页面我们可以自己定义,当未授权的访问的时候跳转至未授权页面。

 定义一个空白html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>
</h1>
</body>
</html>

定义一个方法跳转视图

 @RequestMapping("/unaut")
    @ResponseBody
    public String unauthorized() {
        return "您没有权限访问此页面";

    }

在ShiroConfig中指定这个页面

    @Bean
    public ShiroFilterFactoryBean bean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //注入SecurityManager
        bean.setSecurityManager(securityManager);
        //添加Shiro内置过滤器
        Map<String, String> filterMap= new LinkedHashMap<>();
        //授权: 授权才能操作 只允许带user:add这个字符串的人才能访问add页面
        filterMap.put("/user/add","perms[user:add");
        //authc:认证才能访问
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);
        //没有得到认证的请求会跳转到登录页进行授权
        bean.setLoginUrl("/toLogin");
        //没有得到授权会跳转至未授权页面
        bean.setUnauthorizedUrl("/unaut");
        return  bean;
    }

 未授权的用户再次访问这个页面将被重定向到未经授权的页面。

目前我们只是让add页面会被有权限的人访问到,而这个权限我们还未定义,这里仅仅只是做了拦截而已。

定义权限要在UserRealm的 doGetAuthorizationInfo(PrincipalCollection principalCollection)方法中完成,授权方法和身份认证的单词长得实在太像了,通过参数名更好区分一点。

当我们登录的时候,会走到Realm的doGetAuthenticationInfo(身份认证方法),后台的打印日志也能看到。

获取认证信息

 登录成功再去访问add页面的时候,会走到 doGetAuthorizationInfo(授权方法)后台的打印日志也能看到。

获取授权信息

所以,我们可以在  doGetAuthorizationInfo 中,让走进授权方法的用户,得到授权。

doGetAuthorizationInfo类会返回AuthorizationInfo类型的对象,AuthorizationInfo是个接口,我们会返回它的实现类。

这里用到了 SimpleAuthorizationInfo,这个对象有很多权限相关的方法,之前我们设置了 user:add 才能访问add

所以我们使用 addStringPermission()方法为进入授权方法的用户添加上 user:add 的权限

这样的话,登录的用户就能访问了。

 

    /**
     * 权限相关
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("获取授权信息");
        //SimpleAuthorizationInfo 授予权限的方法 一个固定的套路
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");
        return info;
    }

这里为了初步尝试授权而写死了授权规则,通常通过数据库来获取授权规则。角色不同,对应的权限不同,我们需要对user表进行修改,添加一列perms列,来存放权限相关的内容。

相对于的,POJO也要记得添加上新添加的列为属性。

我们从认证方法中得到的用户的信息,要如何在授权方法中用到他呢?

我们之前在认证方法中 return SimpleAuthenticationInfo对象,第一个参数是省略的,这个参数其实就是我们已经得到认证的用户。

把user扔进这个参数里,再从授权方法中获取。

这样,我们就拿到了认证用户的授权规则,进行有效的授权了。

Shiro整合Thymeleaf 

现在我们需要做的事情是,让能进入add页面能看见add链接,也就是有相应权限的人能看到这个链接。

要完成这个需要Shiro与Thymeleaf的整合。

第一步,导入依赖

第二步,整合ShrioDialect

导入依赖

<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

 整合ShiroDialect  ShiroDialect是thymeleaf-extras-shiro提供的类,我们只需要new一个retrun回去并打上@Bean标签就可以了。

    /**
     * ShiroDailect 用来整合Shiro Thymeleaf
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

 关于Thymeleaf为Shiro整合的标签,thymeleaf-extras-shiro的GitHub上很多例子提供。Github地址:thymeleaf-extras-shiro

总结下来如下:                                                                                                                  参考: shiro权限验证标签

<!--The guest tag-->
<!--验证当前用户是否为“访客”,即未认证(包含未记住)的用户-->
<p shiro:guest="">
  Please <a href="login.html">Login</a>
</p>
<!--The user tag-->
<!--认证通过或已记住的用户。-->
<p shiro:user="">
  Welcome back John! Not John? Click <a href="login.html">here</a> to login.
</p>
<!--The authenticated tag-->
<!--已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。-->
<a shiro:authenticated="" href="updateAccount.html">Update your contact information</a>
<!--The notAuthenticated tag-->
<!--未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。 -->
<p shiro:notAuthenticated="">
    Please <a href="login.html">login</a> in order to update your credit card information.
</p>
<!--The principal tag-->
<!--输出当前用户信息,通常为登录帐号信息。-->
<p>Hello, <span shiro:principal=""></span>, how are you today?</p>
or
<p>Hello, <shiro:principal/>, how are you today?</p>
Typed principal and principal property are also supported.

<!--The hasRole tag-->
<!--验证当前用户是否属于该角色。-->
<a shiro:hasRole="administrator" href="admin.html">Administer the system</a>
<!--The lacksRole tag-->
<p shiro:lacksRole="administrator">
  Sorry, you are not allowed to administer the system.
</p>
<!--The hasAllRoles tag-->
<!--与hasRole标签逻辑相反,当用户不属于该角色时验证通过。-->
<p shiro:hasAllRoles="developer, project manager">
  You are a developer and a project manager.
</p>
<!--The hasAnyRoles tag-->
<!--验证当前用户是否属于以下任意一个角色。 -->
<p shiro:hasAnyRoles="developer, project manager, administrator">
  You are a developer, project manager, or administrator.
</p>
<!--The hasPermission tag-->
<!--验证当前用户是否拥有指定权限。-->
<a shiro:hasPermission="user:create" href="createUser.html">Create a new User</a>
<!--The lacksPermission tag-->
<!--与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。-->
<p shiro:lacksPermission="user:delete">
  Sorry, you are not allowed to delete user accounts.
</p>
<!--The hasAllPermissions tag-->
<p shiro:hasAllPermissions="user:create, user:delete">
  You can create and delete users.
</p>
<!--The hasAnyPermissions tag-->
<p shiro:hasAnyPermissions="user:create, user:delete">
  You can create or delete users.
</p>

所以如果我们想让拥有add的权限的人看见add链接,那么使用hasPermission 的Tag就可以实现,判断它是否有 user:add的授权规则即可。

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
xmlns:shiro=" htttp://thymeleaf.org/thymeleaf-extras-shiro">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<div shiro:hasPermission="user:add">
  <a th:href="@{'/user/add'}">Add</a>
</div>
<a th:href="@{'/user/update'}">Update</a>
</body>
</html>

测试:

未登陆前,只能看见update标签了。

 

使用admin登录(admin有add的权限)

可以试着更完善一点,未登录的时候需要看见登录链接,而登录以后就看不到登录链接了。 这里用到了是 <shiro:guest>标签。

<shiro:guest>
  请 <a th:href="@{/toLogin}">登录</a>
</shiro:guest>

测试效果:

未登录

 

 总结:

一个简单的Shiro与Thymeleaf的整合demo,我们能得到一个步骤思路:

  • 导入Shrio整合Spring的依赖
  • 编写ShroConfig
    • 通过三个方法来获取自定义Realm,securityManager,ShiroFilterFactoryBean。
  • 编写自定义Realm类
    • 认证规则的编写
    • 授权,验证用户名和密码
  • Shrio与Thymeleaf的整合
    • 让前端页面能通过一些标签判断是否有权限访问某些内容。

 


参考:

shiro框架详解  

【狂神说Java】SpringBoot最新教程IDEA版通俗易懂

SpringBoot2.0集成Shiro

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值