快速搭建Spring Boot + Apache Shiro 环境

个人博客网:https://wushaopei.github.io/    (你想要这里多有)

一、Apache Shiro 介绍及概念

概念:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Shiro 框架图:

框架图解析:

Primary Concerns四部分: 用户识别、授权、会话管理、加密

Supporting Features 五部分: Shiro特有的API、缓存、 并发、测试、Run As 、Remember Me

Shiro 概念层 架构:

应用代码经过 Subject 进行简单授权和认证, Subject 又委托给SecurityManager

二、Apache Shiro 部分功能讲解:

1、Apache Shiro授权讲解

  • 首先调用Subject 进行处理,再委托给 Security Manageer ,Security Manageer会委托给 Authorizer ,Authorizer是真正的授权者。

Shiro 的授权主要包含四个核心:主体、资源、权限、角色

主体: 访问应用的用户,用户只有授权后才能访问到相应的资源

资源: 查看、编辑某个数据,读取某个文本等都是资源

权限: 建立在资源以及操作上,只限定能做什么

角色: 才是真正可以对主体的权限进行分配的核心

2、 Apache Shiro权限拦截框架图:

3、Apache Shiro会话管理框架图

会话管理主要是管理所有会话的创建、查询、交互等。

三、基于SpringBoot的Apache Shiro环境快速搭建与配置

1、环境搭建及使用

主要涉及两点:

  • 快速搭建Spring Boot + Apache Shiro 环境
  • 常用Case实现

(1)创建springboot 工程 Spring-boot_Shiro ,整合 Shiro 依赖

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.4.0</version>
</dependency>
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-core</artifactId>
   <version>1.4.0</version>
</dependency>
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.0.20</version>
</dependency>
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.4</version>
</dependency>
<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
</dependency>

(2)创建数据库表结构 : user 、role、permission、permission_role、user_role、五张表;

CREATE TABLE `user` (
`uid`  int(11) NOT NULL AUTO_INCREMENT ,
`username`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`password`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
PRIMARY KEY (`uid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=3
ROW_FORMAT=DYNAMIC;

CREATE TABLE `role` (
`rid`  int(11) NOT NULL AUTO_INCREMENT ,
`rname`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
PRIMARY KEY (`rid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=3
ROW_FORMAT=DYNAMIC;

CREATE TABLE `permission` (
`pid`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`url`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' ,
PRIMARY KEY (`pid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=1
ROW_FORMAT=DYNAMIC;

CREATE TABLE `permission_role` (
`rid`  int(11) NOT NULL ,
`pid`  int(11) NOT NULL ,
INDEX `idx_rid` (`rid`) USING BTREE ,
INDEX `idx_pid` (`pid`) USING BTREE 
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=DYNAMIC;

CREATE TABLE `user_role` (
`uid`  int(11) NOT NULL ,
`rid`  int(11) NOT NULL ,
INDEX `idx_uid` (`uid`) USING BTREE ,
INDEX `idx_pid` (`rid`) USING BTREE 
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=DYNAMIC;

(3)创建 user、role、permission 三个类

public class User {

    private String username;
    private Integer uid;
    private String password;
    private Set<Role> roles = new HashSet<>();
    。。。。。。。。。。。。
}
public class Role {

    private Integer rid;
    private String rname;
    private Set<Permission> permissionSet = new HashSet<>();
    ...................
}

public class Permission {

    private Integer pid;
    private String name;
    private String url;
    。。。。。。。。。。。。。。
}

(4)创建service业务层、mapper 持久层

service 、serviceimpl业务接口及实现类

public interface UserService {

    User findByUsername(String username);
}

@Service
@Transactional(readOnly=true)
public class UserServiceimpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public User findByUsername(String username) {
        return userMapper.findByUsername(username);
    }
}

UserMapper.java 持久层

@Component
@Mapper
public interface UserMapper {

    User findByUsername(@Param("username") String username);

    List<User> getAll();
}

UserMapper.xml

<mapper namespace="com.webcode.springboot.mappers.UserMapper">

    <resultMap id="userMap" type="com.webcode.springboot.entities.User">
        <id property="uid" column="uid"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <collection property="roles" ofType="com.webcode.springboot.entities.Role">
            <id property="rid" column="rid"/>
            <result property="rname" column="rname"/>
            <collection property="permissionSet" ofType="com.webcode.springboot.entities.Permission">
                <id property="pid" column="pid"/>
                <result property="name" column="name"/>
                <result property="url" column="url"/>
            </collection>
        </collection>
    </resultMap>

    <select id="findByUsername" parameterType="string" resultMap="userMap">
        SELECT u.*,r.*,p.*
        from user u
          INNER JOIN user_role ur on ur.uid = u.uid
            INNER JOIN role r on r.rid = ur.rid
          INNER JOIN permission_role pr on pr.rid = r.rid
          INNER JOIN permission p on pr.pid = p.pid
        WHERE u.username = #{username}
    </select>

    <select id="getAll"
            resultMap="userMap">
        select *
        from   USER
    </select>
</mapper>

(5)配置mapper.xml 包扫描位置及对应的javabean 所在包,用于mapper.xml的映射

application.yml 配置文件:

mybatis:
  mapper-locations: classpath*:/mybatis/mappers/*Mapper.xml #包扫描 mapper.xml 类
  type-aliases-package: com.webcode.springboot.entities  # 指定相应 mapper.xml 中实体 javabean 的所在位置
server:
  port: 8081

Bootstrap 启动类:

//扫描Mapper接口
@MapperScan("com.webcode.springboot.mappers")
@ComponentScan({"com.webcode.springboot"})
@SpringBootApplication
public class Bootstrap {
   
   public static void main(String[] args) {
      SpringApplication.run(Bootstrap.class, args);
   }
}

2、基于Apache Shiro权限管理Case -- 认证授权配置

创建自定义的 AuthRealm 类,该类 继承 AuthorizingRealm 类,并重写doGetAutherizationInfo(PrincipalCollection principals) doGetAuthenticationInfo(AuthenticationToken token) 两个方法 分别 作为授权和认证登录所用;

public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 授权要先从 session 取出对象来
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
        List<String> permissionList = new ArrayList<>();
        Set<Role> roleSet = user.getRoles();
        if (CollectionUtils.isNotEmpty(roleSet)){
            for (Role role : roleSet) {
                Set<Permission> permissionSet = role.getPermissionSet();
                if (CollectionUtils.isNotEmpty(permissionSet)){
                    for (Permission permission : permissionSet) {
                        permissionList.add(permission.getName());
                    }
                }
            }
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addStringPermissions(permissionList);
        return null;
    }

    // 认证登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; //将Token 降为 usernamePasswordToken
        String username = usernamePasswordToken.getUsername();    //去除其中的username
        User user = userService.findByUsername(username);       //根据username 查询到 user 信息
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
    }
}

3、基于Apache Shiro权限管理Case -- 密码规则校验器

说明

因为密码是直接取得数据库的密码,所以想要保证用户传入的密码能和我的密码能够匹配,需要创建自定义的密码校验规则。

具体:通过重写CredentialsMatcher 类,继承SimpleCredentialsMatcher类,重新定义 密码校验规则的重写

/**
 * @ClassName CredentialMatcher 密码校验规则的重写
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/9/24 10:07
 * @Version 1.0
 */
public class CredentialMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken token1 = (UsernamePasswordToken) token;
        String passsword = new String(token1.getPassword());
        String dbPassword = (String) info.getCredentials();
        return this.equals(passsword,dbPassword);
    }
}

4、基于Apache Shiro权限管理Case -- Shiro配置

创建 ShiroConfiguration 类 ,该类是 shiro 的配置类

@Configuration
public class ShiroConfiguration {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager){

        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        // 必须设置 SecurityManager
        bean.setSecurityManager(manager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        bean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        bean.setSuccessUrl("/index");
        // 未授权界面;
        bean.setUnauthorizedUrl("/unauthorized");

        // 拦截器.
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }
    //配置核心安全事务管理器
    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(authRealm);
        return securityManager;
    }
    //配置自定义的权限登录器
    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher")CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher); //给出自定密码比较器
        return authRealm;       //返回自定Realm
    }
    //配置自定义的密码比较器
    @Bean("credentialMatcher")  //生成 密码校验规则的Bean
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher(); //返回密码校验规则实例
    }
    /**
     * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; Controller才能使用@RequiresPermissions
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

5、定义请求接口:包括首页、登录页、异常页面

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

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

@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username")String username,
                        @RequestParam("password") String password,
                        HttpSession session){
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(token);
        User principal = (User) subject.getPrincipal();
        session.setAttribute("user",principal);
        return  "index";
    }catch (Exception e){
        return "login";
    }
}

6、定义jsp 页面,包括index.jsp、login.jsp、

jsp 相关配置在 yml 文件中,申明jsp的前缀、后缀

spring:
  mvc:  ## jsp ##
    view:
      prefix: /pages/
      suffix: .jsp

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Login</title>
</head>
<body>
<h1>欢迎登录</h1>
<form action="/loginUser" method="post">
    <input type="text" name="username"><br>
    <input type="password" name="password"><br>
    <input type="submit" value="提交"><br>
</form>
</body>
</html>

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
<h1>欢迎登录, ${user.username}</h1>
</body>
</html>

接口登录测试:

提交,认证通过

7、基于Apache Shiro权限管理Case -- Shiro配置 登录拦截

shiro 过滤器添加配置:

   filterChainDefinitionMap.put("/loginUser","anon"); //登陸不用登陸認證
   filterChainDefinitionMap.put("/**","user"); //其他接口要驗證是否登陸過用戶

测试:

除了登录功能不需要进行认证外,其他接口请求都需要进行用户是否登录的认证.

8、基于Apache Shiro权限管理Case -- Shiro配置 角色认证

在 ShiroConfiguration.java 进行配置:

filterChainDefinitionMap.put("/admin","roles[admin]"); //授权过程中认证角色为admin的用户才可以访问

在AuthRealm.java进行配置:

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 授权要先从 session 取出对象来
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
        List<String> permissionList = new ArrayList<>();
        List<String> roleNameList = new ArrayList<>();        //角色授权拦截。。。。。。。
        Set<Role> roleSet = user.getRoles();
        if (CollectionUtils.isNotEmpty(roleSet)){
            for (Role role : roleSet) {
                roleNameList.add(role.getName());            //角色授权拦截。。。。。。。。
                Set<Permission> permissionSet = role.getPermissionSet();
                if (CollectionUtils.isNotEmpty(permissionSet)){
                    for (Permission permission : permissionSet) {
                        permissionList.add(permission.getName());
                    }
                }
            }
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addStringPermissions(permissionList);
        simpleAuthorizationInfo.addRoles(roleNameList);     //角色授权拦截。。。。。。
        return simpleAuthorizationInfo;
    }

8、基于Apache Shiro权限管理Case -- Shiro配置 permission 认证

说明:具有指定 permission 功能的permission才可以访问,如有edit时,才可以访问,否则不能访问

在ShiroConfiguration.java类中 shiroFilter 方法中配置:

        filterChainDefinitionMap.put("/edit","perms[edit]");//具有edit 的pemission 才可以访问
原文地址:https://www.cnblogs.com/wushaopei/p/11681408.html