51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

SpringBoot整合Shiro详解,还在自己写登陆注册早落伍了

# (一)概述 {#一-概述}

Shiro是Apache的一个安全框架,Shiro可以非常容易的开发出安全性足够好的应用,Shiro可以完成认证、授权、加密、会话管理、缓存等功能。 从应用程序的角度来观察Shiro,我们可以发现Shiro的运行过程主要如下:

应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

Subject:主体,代表了当前"用户",这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

# (二)SpringBoot整合Shiro {#二-springboot整合shiro}

首先引入Shiro依赖

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

通过上面的这些概念,我们开始来写代码,使用Shiro只需要三步

1.创建realm对象,需要自定义

2.创建安全管理器,绑定realm对象

3.创建Shiro过滤工厂,绑定安全管理器

首先第一步,自定义一个realm对象

public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

定义一个UserRealm继承AuthorizingRealm 并实现接口方法,两个方法分别代表授权和认证,这里的实现方式和SpringSecurity十分类似。

接下来创建一个ShiroConfig类,来实现shiro的配置

@Configuration
public class ShiroConfig {
    //创建realm对象,需要自定义
    @Bean(name = "userRealm")
    public UserRealm userRealm(){
        return new UserRealm();
    }
    //创建安全管理器
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //绑定realm对象
        securityManager.setRealm(userRealm());
        return securityManager;
    }
    //创建Shiro过滤工厂
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }
}

# (三) shiro实现登陆拦截 {#三-shiro实现登陆拦截}

在Shiro实现登陆拦截只需要在shiro的过滤工厂配置过滤器即可,为了更加具体的展示效果,我们需要新建四个html页面:index、level1、level2、login

另外需要引入thymeleaf依赖以及spring-boot-starter-web依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

index.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><!--引入thymeleaf-->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<h2 th:text="${msg}"></h2>
<a th:href="@{/level1}">level1</a>
<a th:href="@{/level2}">level2</a>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登陆页</title>
</head>
<body>
<div>
    <p th:text="${errormsg}"></p>
    <form action="/checklogin" method="post">
        <h2>登陆页</h2>
        <input type="text" id="username"  name="username" placeholder="username">
        <input type="password" id="password" name="password"  placeholder="password">
        <button type="submit">登陆</button>
    </form>
</div>
</body>
</html>

level1、level2

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

然后编写indexController

@Controller
public class indexController {
    @RequestMapping({"/","/index"})
    public String index(Model model){
        model.addAttribute("msg","hello,shiro");
        return "index";
    }
    @RequestMapping("/level1")
    public String level1(Model model){
        return "level1";
    }
    @RequestMapping("/level2")
    public String level2(Model model){
        return "level2";
    }
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String tologin(){
        return "login";
    }
}

在这些前期工作完成后,我们就可以配置过滤拦截器了,在ShiroConfig类中的ShiroFilterFactoryBean 方法中,添加所需要配置的过滤器,shiro内置了五种过滤器

/**
 * anon : 无需认证就可以访问
 * authc: 必须认证了才能访问
 * user: 必须有 记住我 功能才能访问
 * perms: 拥有对某个资源的权限才能访问
 * role: 拥有某个角色权限才能访问
 */

在使用时,只要将对应的页面分配不同的过滤器即可

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
    //设置安全管理器
    bean.setSecurityManager(defaultWebSecurityManager);
    //添加shiro的内置过滤器
    Map<String,String> filterMap=new LinkedHashMap<>();
    filterMap.put("/index","anon");
    filterMap.put("/level1","authc");
    filterMap.put("/level2","authc");
    bean.setFilterChainDefinitionMap(filterMap);
    //设置跳转登陆页
    bean.setLoginUrl("/login");
    return bean;
}

在这段代码中,我们设置index首页无需权限就可以访问,level1何level2需要认证了才能访问,登陆页跳转到login页面。实现的效果为进入index首页无需任何认证,当点击level1和level2标签时判断有无认证,如果没有认证自动跳转到login页面。

# (四)Shiro实现用户认证 {#四-shiro实现用户认证}

shiro的用户认证都放在realm中进行,首先我们需要改造一下controller中对登陆逻辑的处理,我们需要将用户传过来的用户名和密码封装到shiro中,新写一个方法判断登陆情况

@RequestMapping(value = "/checklogin",method = RequestMethod.POST)
public String login(@RequestParam("username") String username,@RequestParam("password") String password,Model model){
    //获取当前的用户
    Subject subject = SecurityUtils.getSubject();
    //用来存放错误信息
    String msg="";
    //如果未认证
    if (!subject.isAuthenticated()){
         //将用户名和密码封装到shiro中
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            // 执行登陆方法
            subject.login(token);
        }catch (UnknownAccountException e){//用户名不存在
            msg=e.getMessage();
        }catch (IncorrectCredentialsException e){//密码错误
            msg=e.getMessage();
        }catch (Exception e){
            msg="用户登陆异常";
            e.printStackTrace();
        }
        //如果msg为空,说明没有异常,就返回到主页
        if (msg.isEmpty()){
            return "redirect:/index";
        }else {
            model.addAttribute("errormsg",msg);
            return "login";
        }
    }
    return "login";
}

在这个登陆控制器中,我们首先通过subject判断用户是否存在,如果不存在就封装用户数据进行login。这个login的动作就需要在realm中去执行。回到UserRealm的认证方法:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //获取到token
    UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
    //从token中获取到用户名和密码
    String username = token.getUsername();
    String password = String.valueOf(token.getPassword());
    //为了方便,这里不从数据库中获取用户
    if (!"root".equals(username)) {
        throw new UnknownAccountException("用户不存在");
    }else if (!"123456".equals(password)){
        throw new IncorrectCredentialsException("密码错误");
    }
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName());
    return info;
}

在之前的代码中我们将用户名和密码存入了UsernamePasswordToken里,在UserRealm中可以通过authenticationToken获取到,获取到用户名和密码之后,我们可以将用户名和密码与数据库进行对比,并向上抛出不同的异常,这些异常会被e.getMessage()所获取到。

这里需要注意的是这个返回值SimpleAuthenticationInfo ,这个类是AuthenticationInfo 的实现类,SimpleAuthenticationInfo的构造方法需要传入三个参数:

第一个参数是principal,一般会传入用户名或者用户实体类,然后在其他地方通过下面这段代码获取到当前登陆的用户

SecurityUtils.getSubject().getPrincipal();

第二个参数是密码,注意这里是指数据库中的密码,因为我们在前面的代码中已经做了一层密码判断,这里的密码校验就没有多大效果。

第三个参数是Realm的名称,直接使用getName()方法获取即可。

# (4.1)效果展示 {#_4-1-效果展示}

进入首页后点击level1或者level2自动跳转登陆页

进入登陆页后如果用户名输入错误显示用户不存在,密码错误则展示密码错误

当用户名和密码都输入正确后进入首页,此时点击level1或者level2都进入两个页面。

# (五)Shiro实现用户授权 {#五-shiro实现用户授权}

在前面我们实现了用户的认证,接下来我们来做用户授权,用户授权在很多场景下都可以见到,比如一个网站的某些页面只有vip可以见到。

我们来模拟上面的这个场景,首先修改ShiroConfiggetShiroFilterFactoryBean方法,增加权限

 @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        Map<String,String> filterMap=new LinkedHashMap<>();
        //level1只有vip1才能访问
        filterMap.put("/level1","perms[vip1]");
        filterMap.put("/index","anon");
        bean.setFilterChainDefinitionMap(filterMap);
        bean.setLoginUrl("/login");
        //如果没有权限跳转的页面
        bean.setUnauthorizedUrl("/unauthorizedUrl");
        return bean;
    }

level1只有vip1才能访问,如果没有权限就会跳转到/unauthorizedUrl,我们先把这个跳转后的路径写好:

@RequestMapping("/unauthorizedUrl")
@ResponseBody
public String unAuthorizedUrl(){
    return "当前用户没有权限访问";
}

接着在UserRealm中编写授权部分的代码:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //在这里为每一个用户添加vip1权限
    info.addStringPermission("vip1");
    return info;
}

在这段代码中通过addStringPermission为所有用户增加了vip1权限,在真实的环境中,我们会根据不同的用户给出不同的权限,比如在数据库中增加一个权限字段,上面代码中填写vip1的地方用数据库中的权限字段代替。

# (六)总结 {#六-总结}

至此,我们已经将shiro的引入、基本使用、认证、授权都学完一遍了。Shiro和SpringSecurity各有优点,具体选择用哪个框架,看你们公司的选择吧。

赞(5)
未经允许不得转载:工具盒子 » SpringBoot整合Shiro详解,还在自己写登陆注册早落伍了