Shiro简介

Shiro是Java的一个安全框架,可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境,Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。Shiro有很多的功能:
整合Shiro - 图1
Authentication:身份认证/登录,验证用户是不是拥有相应的身份
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色或细粒度的验证某个用户对某个资源是否具有某个权限
Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
Web Support:Web 支持,可以非常容易的集成到Web 环境
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
Testing:提供测试支持
Run As“允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

Shiro有三个主要的对象:
整合Shiro - 图2

  • Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它当前和软件交互的任何事件。
  • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
  • Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

我们需要实现Realms的Authentication(认证) 和 Authorization()授权。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。

搭建框架

导入依赖

  1. <dependency>
  2. <groupId>org.apache.shiro</groupId>
  3. <artifactId>shiro-spring</artifactId>
  4. <version>1.5.3</version>
  5. </dependency>

环境准备

控制器:

@Controller
public class UserController {

    @RequestMapping("/user/toLogin")
    public String toLogin() {
        return "views/login";
    }

    @RequestMapping("/user/toIndex")
    public String toIndex() {
        return "index";
    }

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

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

    @RequestMapping("/user/noAuto")
    @ResponseBody
    public String noAuto(){
        return "您没有访问该操作的权限,请返回!";
    }

    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            //当前用户登录成功就跳转到index.html
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            //UnknownAccountException代表用户名错误
            model.addAttribute("errMsg", "用户名错误");
            return "views/login";
        } catch (IncorrectCredentialsException e) {
            //IncorrectCredentialsException代表密码错误
            model.addAttribute("errMsg", "密码错误");
            return "views/login";
        }
    }

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id) {
        return "views/level1/" + id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id) {
        return "views/level2/" + id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id) {
        return "views/level3/" + id;
    }
}
@Repository
public interface UserMapper {

    /**
     * 通过name获取user
     *
     * @param name 用户名
     * @return User对象
     */
    User getUserByName(@Param("name") String name);
}
<div class="ui secondary menu">
            <a class="item" th:href="@{/index}">首页</a>

            <div class="right menu">

                    <a class="item" th:href="@{/user/toAdd}">
                        <i class="plus icon"></i> add
                    </a>

                    <a class="item" th:href="@{/user/toUpdate}">
                        <i class="blind icon"></i> update
                    </a>

                    <a class="item" th:href="@{/user/toLogin}">
                        <i class="blind icon"></i> 登录
                    </a>

            </div>
</div>

主页面大致:
image.png
user类:

public class User {
    private Integer id;
    private String name;
    private String pwd;
    private String permission;

    构造器和getXxx、setXxx

}

user表:
image.png

编写配置类和Realm类

我们首先需要自定义编写Shiro的Realm类来完成认证和授权操作:

public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

     //认证操作
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("进入到认证方法 ==》doGetAuthenticationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //从数据库中查询user对象
        User user = userService.getUserByName(userToken.getUsername());

        if (user == null) {
            return null;
        }

        // 密码认证,shiro做
        return new SimpleAuthenticationInfo(user, user.getPwd(), "userRealm");
    }

    //授权操作
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进入到授权方法 ==》doGetAuthorizationInfo");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //获取已经过认证的user,从认证方法中传递的参数获取---》new SimpleAuthenticationInfo(user, user.getPwd(), "")
        User user = (User) subject.getPrincipal();
        //user.getPermission()是获取user对象中的权限,从数据库中查询
        simpleAuthorizationInfo.addStringPermission(user.getPermission());
        return simpleAuthorizationInfo;
    }
}

其中“new SimpleAuthenticationInfo(user, user.getPwd(), “”)”:
第一个参数user,一般是user对象。
第二个参数是user.getPassword(),注意这里是指从数据库中获取的password。
第三个字段是realm,即当前realm的名称,可以设置为空字符串。

然后需要注册三个核心对象:ShiroFilterFactoryBean、DefaultWebSecurityManager、UserRealm,我们通常再ShiroFilterFactoryBean中进行登录过滤和权限过滤,如:

@Configuration
public class ShiroConfig {

    /**
     * 把ShiroFilterFactoryBean注入Spring
     *
     * @param webSecurityManager 注入到Spring的DefaultWebSecurityManager
     * @return shiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager webSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(webSecurityManager);

        Map<String, String> filterChainDefinitionMap=new LinkedHashMap<>();

        //判断当前用户是否具有访问资源的权限
        filterChainDefinitionMap.put("/user/toAdd","perms[user:add]"); //需要user:add权限
        filterChainDefinitionMap.put("/user/toUpdate","perms[user:update]"); //需要user:update权限

        //设置登录过滤器
        filterChainDefinitionMap.put("/user/*","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //没有权限的跳转地址
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuto");
        //没有登录的跳转地址
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");

        return shiroFilterFactoryBean;
    }

    /**
     * 把DefaultWebSecurityManager注入Spring,并设置Realm
     *
     * @param userRealm 注入到Spring的自定义Realm
     * @return webSecurityManager
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
        webSecurityManager.setRealm(userRealm);
        return webSecurityManager;
    }

    /**
     * 把自定义的Realm对象注入Spring
     *
     * @return new UserRealm()
     */
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

效果展示
登录root用户:
image.png
点击add操作:
image.png
点击update操作:
image.png

结合Thymeleaf模板引擎

和Spring Security一样,Shiro也可以结合Thymeleaf模板引擎使用,首先需要导入依赖:

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

实例:

 <div class="right menu">
       <!--具有user:add权限才展示-->
       <div shiro:hasPermission="user:add">
          <a class="item" th:href="@{/user/toAdd}">
             <i class="plus icon"></i> add
          </a>
       </div>

       <!--具有user:update权限才展示-->
       <div shiro:hasPermission="user:update">
          <a class="item" th:href="@{/user/toUpdate}">
             <i class="blind icon"></i> update
          </a>
       </div>

       <!--用户没有完成认证时显示登录按钮,完成了则不显示-->
       <div shiro:notAuthenticated>
          <a class="item" th:href="@{/user/toLogin}">
              <i class="blind icon"></i> 登录
          </a>
        </div>
</div>

效果展示(登录root用户):
image.png
可以看到没有显示登录和add,只显示了具有add:update权限的update