SpringBoot整合shiro

这几天在学Apache的shiro,现在简单记录下学习过程,以及spring boot简单的整合shiro例子


shiro

Apache Shiro 是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。而且 Shiro 的 API 也是较为简单;

Shiro Features 特性

Apache Shiro 是一个全面的、蕴含丰富功能的安全框架。Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)为 Shiro的四大基石。

  • Authentication(认证):用户身份识别,通常被称为用户“登录”
  • Authorization(授权): 访问控制。比如某个用户是否具有某个操作的使用权限。
  • Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
  • Cryptography(加密): 在对数据源使用加密算法加密的同时,保证易于使用。

shiro架构

在这里插入图片描述
shiro架构主要包括Subject,SecurityManager,Realm

  • Subject: 当前用户
  • SecurityManager: 管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
  • Realms: 用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

Tips: Shiro 不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给 Shiro


SpringBoot整合shiro

pom文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <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.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

数据库配置

spring:
  datasource:
    username: root
    password: 1001101
    url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8
    initialization-mode: always
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database: mysql
  thymeleaf:
    cache: false
    mode: HTML

本案例使用RBAC设计
使用JPA来自动生成表,对于的实体类如下:
用户表


@Entity
public class User {
    @Id
    @GeneratedValue
    private int id;
    @Column(unique = true)
    private String name;
    private String password;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "UserRole",joinColumns = {@JoinColumn(name = "uid")},inverseJoinColumns = {@JoinColumn(name = "roleId")})
    private List<Role> roleList;//一个用户有多个角色
    //get,set方法省略,下同

角色表

@Entity
public class Role {
    @Id
    @GeneratedValue
    private int id;
    private String role;
    private String description;

    @ManyToMany
    @JoinTable(name="UserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
    private List<User> userInfos;// 一个角色对应多个用户

    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<Premisson> permissions;

权限表

@Entity
public class Premisson {
    @Id
    @GeneratedValue
    private int id;
    private String name;//名称
    private String url;
    private String permission;




    @ManyToMany
    @JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<Role> Roles;

运行后数据库会生成五张表
在这里插入图片描述
为了方便测试,我们要往其中添加一些数据进行测试,所有的sql语句我会放在GitHub中

shiroConfig

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        System.out.println("shiro过滤器");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String,String> filterMap = new LinkedHashMap<>();
        //anon为匿名访问不需要认证即可访问,authc需要认证才可访问
        filterMap.put("/static/**","anon" );
        filterMap.put("/regi","anon" );
        filterMap.put("/register","anon" );
         filterMap.put("/logout", "logout");//shiro内部自动帮我们执行退出,移除session,然后跳转到.setLoginUrl配置的页面
        filterMap.put("/login.action","anon" );
        //一般这个都放在最后一个
        filterMap.put("/**","authc" );
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
//        shiroFilterFactoryBean.setUnauthorizedUrl("403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 自定义加密策略,加密算法为MD5,加密次数为2次
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     * 设置加密策略为自定义的加密策略
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}
  1. anon:所有 url 都都可以匿名访问
  2. authc: 需要认证才能进行访问
  3. user:配置记住我或认证通过可以访问

自定义Realm
认证的实现
因为在 Shiro 中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。通常情况下,在 Realm 中会直接从我们的数据源中获取 Shiro 需要的验证信息。可以说,Realm 是专用于安全框架的 DAO. Shiro 的认证过程最终会交由 Realm 执行,当执行subject.login时就会调用 Realm 的doGetAuthenticationInfo(AuthenticationToken token) 方法进行认证

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("-----认证----");
        UsernamePasswordToken token1 = (UsernamePasswordToken) token;
        String name =  token1.getUsername();//获得前端用户输入的名字
        User user = userDao.findByName(name);//从数据库查询,返回用户
        if(null == user){
            return null;
        }
        Object salt = ByteSource.Util.bytes(name);//加用户名为盐
        return new SimpleAuthenticationInfo(user,user.getPassword(), (ByteSource) salt,getName());
    }

这里使用的MD5加盐对密码进行加密,盐一般设置为用户名,这样即使密码一样,最后存入到数据库的密文也是不同的
new SimpleAuthenticationInfo(user,user.getPassword(), (ByteSource) salt,getName()); 对密码进行检验

执行过程图解
在这里插入图片描述
权限认证
Shiro 的权限授权是通过Realm重载 doGetAuthorizationInfo(); 方法,当访问到页面的时候,链接配置了相应的权限或者 Shiro 标签才会执行此方法否则不会执行,是个懒加载。SimpleAuthorizationInfo 进行角色的添加和权限的添加。

 @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user  = (User) principals.getPrimaryPrincipal();
        for(Role role:user.getRoleList()){//从当前的用户获取该用户具有的所有角色
            authorizationInfo.addRole(role.getRole());
            for(Premisson p:role.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());//添加权限
            }
        }
        return authorizationInfo;
    }

注册用户
当用户注册时,对密码的加密算法要与shiro的相同,否则认证的时候永远都是密码错误

@RequestMapping("/register")
    public String register(User user){
        System.out.println(user.getName()+"---"+user.getPassword());
        String password = MD5Utils.md5(user);
        user.setPassword(password);
        userDao.save(user);
        return "login";
    }

//加密算法
public class MD5Utils {
    public static String md5(User user){
        String name = "MD5";//MD5加密
        String pwd = user.getPassword();
        ByteSource salt = ByteSource.Util.bytes(user.getName());//用户名为盐值
        int num = 2;//加密次数

        Object result = new SimpleHash(name,pwd ,salt ,num );
        System.out.println(result);
        return result.toString();
    }
}

登录
subject.login进行登录认证

@RequestMapping("/login.action")
    public String login(String username, String password, Model model, HttpSession session){
        Subject subject = SecurityUtils.getSubject();
        //new一个 UsernamePasswordToken,并传上用户名及密码。把返回值传给登入作为条件。
        UsernamePasswordToken token = new UsernamePasswordToken(username,password );
        try{
            subject.login(token);
            System.out.println(subject.isAuthenticated());
            User user = (User) subject.getPrincipal();
            session.setAttribute("user",user );
            return "main";
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误" );
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误" );
            return "login";
        }
    }

异常处理类
当产生UnauthorizedException异常时,即某个用户访问某个没有权限的页面时,返回403页面

@ControllerAdvice
public class ExceptionHandler {

    @org.springframework.web.bind.annotation.ExceptionHandler(UnauthorizedException.class)
    public ModelAndView exc(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("403");
        return modelAndView;
    }
}

运行程序
注册页面
在这里插入图片描述
数据库保存的密文
在这里插入图片描述
登录
因为为普通用户,所有只有用户查看的权限
在这里插入图片描述
在这里插入图片描述
换成admin用户就要除了vip的所有权限
在这里插入图片描述
退出
在这里插入图片描述

完整的代码在我的GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lpepsi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值