springboot整合shiro

1.shiro概念

  • Shiro安全框架是Apache提供的一个强大灵活的安全框架
  • Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能

 2.shiro架构

  • Subject:应用代码直接交互的对象是Subject,与Subject的所有交互都会委托给SecurityManage,Subject是一个门面,SecurityManage才是执行者
  • SecurityManage:安全管理器,用来处理所有有关安全的操作,并且管理所有的Subject,它负责与Subject交互,相当于SpringMvc里的Dispatchservlet
  • Realm: Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库,那么realm就需要从数据库获取用户身份信息。在realm中还有认证授权校验的相关的代码

 3.认证功能

项目目录如下:

注意:springboot的版本用2.x.x的,用最新的版本,项目可能跑不出来,如果有大佬看见了,并且用3.x.x的版本跑出来了,还请留言,麻烦告知下本菜鸡解决方案>_< 

 3.1.引入依赖

导入的依赖有:

  • springboot整合shiro
  • thymeleaf启动器
  • springboot启动器 
        <!--shiro整合springboot-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <!--thymeleaf启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

3.2.编写页面和controller层

index.html:

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


<a th:href="@{/user/add}" style="color: red">add</a>  |  <a th:href="@{user/update}" style="color: red">update</a>
</body>
</html>

login.html: 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<form th:action="@{/login}">
  <input type="text" name="username">
  <input type="text" name="password">
  <input type="submit">
</form>
</body>
</html>

controller层: 

@Controller
public class MyController {
    @RequestMapping("/index")
    public String toIndex(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";
    }

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

3.3.创建Realm 

根据上面的架构可知,要想使用Shiro,我们得先创建一个Realm,如下:

package com.kuang.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了-->授权");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了-->认证");
        return null;
    }
}

3.4.创建SecurityManage和ShiroFilterFactoryBean 

紧接着我们来创建SecurityManage和ShiroFilterFactoryBean,如下:

  • 这里的ShiroFilterFactoryBean是用来创建ShiroFilter的,它可以创建Shiro的过滤器链,然后打包交给ShiroFilter,并且将其交给Servlet,实现页面过滤和拦截功能
  • 下面的代码在ShiroFilterFactoryBean这个类中,实现了过滤器的功能
package com.kuang.config;
import com.kuang.config.UserRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

import java.util.Map;

@Configuration
public class ShiroConfig {

    //shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        Map<String, String> FilterMap = new HashMap<>();

        FilterMap.put("/user/add","authc");
        FilterMap.put("/user/update","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(FilterMap);

        return shiroFilterFactoryBean;
    }


    //DefaultWebSecurityManage
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //这个方法的参数需要一个realm类型的数据,我们就用自己定义的userRealm
        //怎么样使用自定义的这个userRealm呢?
        //既然我们自定义的userRealm已经交给spring托管了
        //那么我们只需要让参数注入到这个userRealm的bean中即可
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建realm对象(需要自定义)
    //这样,我们自己写的这个类就被spring托管了
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

}

 3.5.实现用户认证

在controller包下实现这个功能,当用户登录时,若密码或者用户名不正确,则会跳转对应的页面

@RequestMapping("/login")
    public String login(String username,String password,Model model){
        System.out.println("123456");
        //获取当前用户对象
        Subject subject = SecurityUtils.getSubject();
        //封装用户登录的数据并交给令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            //执行登陆方法,如果没有异常就说明ok
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {//这个异常表明用户名不存在
            model.addAttribute("msg","用户名不存在");
            return "login";
        }catch (IncorrectCredentialsException e){ //这个异常表示密码不存在
           model.addAttribute("msg","密码不存在");
           return "login";
        }

    }
  • 上面的方法,接收了前端传来的参数,username和password
  • 上面的方法有几个重要的代码:
  • Subject subject = SecurityUtils.getSubject(); 获取subject用户只有获取了subject才能实现shiro的相关功能
  • UsernamePasswordToken token = new UsernamePasswordToken(username, password);将获取的username和password交给token令牌(令牌,顾名思义就是存有用户数据的一个集合)

 3.6.代码执行细节

当程序执行了上面的用户认证功能之后,shiro会把从token里获取到的数据同存放在数据库的用户数据进行对比,那么从哪里获取存放在数据库里的用户数据呢?答案是,我们自己创建的Realm

4.shiro整合mybatis(注意依赖版本)

4.1.引入依赖

上面说了,如果要让shiro和数据库里的数据进行比对,则就要使用数据库,那么就要导入相关的依赖

  • springboot整合mybatis
  • mysql驱动
  • 数据库驱动
  • lombok
        <!--整合mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.2</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!--数据库驱动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

注意:我用的springboot的版本是2.7.18的,所以springboot整合mybatis的依赖的版本也比较低是2.3.2的

4.2.编写查询用户的业务代码

项目目录:

4.2.1.编写pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}
4.2.2.UserMapper
//这个注解表示了这是一个mybatis的mapper类
@Mapper
//这个注解表明将这个类注入到了spring中
@Repository
public interface UserMapper {

    List<User> selectAllUser();

    User selectUserByName(String name);

}
4.2.3.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.kuang.mapper.UserMapper">
   <select id="selectAllUser" resultType="user">
       select * from tb_user
   </select>

    <select id="selectUserByName" parameterType="String" resultType="user">
        select * from tb_user where name = #{name}
    </select>
</mapper>
4.2.4.Service层
public interface UserService {
    List<User> selectAllUser();

    User selectUserByName(String name);

}
@Service
public class UserServiceImpl implements UserService{

    @Autowired
    UserMapper userMapper;

    @Override
    public List<User> selectAllUser() {
        return userMapper.selectAllUser();
    }

    @Override
    public User selectUserByName(String name) {
        return userMapper.selectUserByName(name);
    }
}
4.2.5.测试

引入数据库之后,我们来测试一下有没有成功引入,在springboot测试类中编写

    @Autowired
    UserServiceImpl userService;
    
    @Test
    void contextLoads() throws SQLException {
        System.out.println("11111");
        System.out.println(userService.selectUserByName("小刚"));
    }

5.编写自定义的Realm 

//认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了-->认证");

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        //连接真实数据库
        User user = userService.selectUserByName(usernamePasswordToken.getUsername());

        if (user==null){//如果用户名为空,抛UnknownAccountException
            return null;
        }
        //密码(对照)认证,调用SimpleAuthenticationInfo方法,shiro自动帮我们做密码认证
        //返回true表明密码正确,返回false则抛出IncorrectCredentialsException异常,从MyController中就可以接收到
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }

 分析下上面的代码:

  • UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; 将参数authenticationToken类型转换成UsernamePasswordToken 
  • User user = userService.selectUserByName(usernamePasswordToken.getUsername());连接真实数据库,用usernamePasswordToken调用getUsername方法,得到token里存的从前端传来的username
  • if (user==null){
        return null;
    }
    return new SimpleAuthenticationInfo("",user.getPwd(),"");  进行判断,如果用户名为空就抛出UnknownAccountException的异常,并且返回给MyController里对应的方法
    

测试:

登陆页面

 当用户名错误时

当密码错误时

当用户名和密码都没有错时

登陆成功,跳转到首页

6.授权功能 

什么叫做授权,举个实际的例子,一个公司有普通员工经理总裁三个职位,当普通员工登录该公司的人员管理网站时,只能看到自己的信息,而经理不仅能看到自己的信息,还能看到他所管的部门的所有的普通员工的信息,而总裁能看到公司所有人的信息,根据不同的职位,所能进入的人员信息页面是不一样的,这就用了授权功能

6.1.在ShiroFilterFactoryBean中添加过滤器

记住:ShiroFilterFactoryBean这个类主要就是来编写过滤器的,先将指定的页面进行拦截,最后再在自定义的Realm类中进行授权

//shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        Map<String, String> FilterMap = new HashMap<>();
        //anon 无需认证就可以访问
        //authc  必须认证了才能访问
        //user  必须拥有记住我功能才能用
        //perms  拥有对某个资源的权限才能用
        //role  拥有某个角色权限才能访问

        //FilterMap.put("/user/add","authc");
        //FilterMap.put("/user/update","authc");

        //设置授权,正常情况下未授权会跳转到未授权的页面
        //必须有[user:add]才能访问/user/add这个页面

        FilterMap.put("/user/add","perms[user:add]");
        FilterMap.put("/user/update","perms[user:update]");

        FilterMap.put("/user/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(FilterMap);
        //设置未授权的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

        return shiroFilterFactoryBean;
    }

6.2.在Realm类中进行授权

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了-->授权");
        //要给用户授权,必须先要创建SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //为什么要创建subject,因为我们要获取用户表里的授权数据
        Subject subject = SecurityUtils.getSubject();
        //在认证功能中,最终会返回一个principal,而刚好有getPrincipal方法
        //这样,我们在认证功能中将查询出来的用户数据交给principal,在这就能获取到
        User principal =(User) subject.getPrincipal();
        //设置当前用户的权限
        info.addStringPermission(principal.getPerms());

        //info.addStringPermission("[user:add]");

        return info;
    }

注意:在这个类中的getPrincipal方法,其获取的参数,要在另一个类设置好,如下:

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了-->认证");

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        //连接真实数据库
        User user = userService.selectUserByName(usernamePasswordToken.getUsername());

        if (user==null){//如果用户名为空,抛UnknownAccountException
            return null;
        }
        //密码(对照)认证,调用SimpleAuthenticationInfo方法,shiro自动帮我们做密码认证
        //返回true表明密码正确,返回false则抛出IncorrectCredentialsException异常,从MyController中就可以接收到
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }

 

6.3.设置未授权的页面

当我们没有进入某个页面的权限时,就会跳转到未授权的页面,有小伙伴可能要问了,既然没有权限,那就不要在前端显示进入这个页面的链接了呀,ok,确实如此,我们后面会实现的>_<

 @RequestMapping("/noAuth")
    @ResponseBody
    public String unAuthorization(){
        return "未授权";
    }

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值