欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot中集成Shiro

程序员文章站 2022-03-20 11:09:07
...

1.shiro简介

Apache Shiro是一个功能强大且灵活的开源安全框架,可以干净地处理身份验证,授权,企业会话管理和加密。

2.shiro功能介绍

Apache Shiro是具有许多功能的全面的应用程序安全框架。下图显示了Shiro核心功能

Spring Boot中集成Shiro

Shiro以Shiro开发团队所谓的“应用程序安全性的四个基石”为目标-身份验证,授权,会话管理和密码术:

  • **身份验证:**有时称为“登录”,这是证明用户就是他们所说的身份的行为。
  • **授权:**访问控制的过程,即确定“谁”有权访问“什么”。
  • **会话管理:**即使在非Web或EJB应用程序中,也可以管理用户特定的会话。
  • **密码术:**使用密码算法保持数据安全,同时仍然易于使用。

3.shiro核心组件

  • Subjectorg.apache.shiro.subject.Subject
    当前与软件交互的实体(用户,第三方服务,计划任务等)的特定于安全性的“视图”。(把操作交给SecurityManager)
  • SecurityManagerorg.apache.shiro.mgt.SecurityManager
    安全管理器(关联Realm)
  • Realm(org.apache.shiro.realm.Realm
    领域充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。当真正需要与安全性相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个Realms中查找许多此类内容。您可以根据Realms需要配置任意数量(通常每个数据源一个),并且Shiro会根据需要进行协调,以进行身份验证和授权。

4.shiro实战

4.1 环境构建

1.maven依赖:

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

2.自定义Realm

自定义 realm 需要继承 AuthorizingRealm 类,因 为该类封装了很多方法,它也是一步步继承自 Realm 类的,继承了 AuthorizingRealm 类后,需要重写 两个方法:

doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息

doGetAuthorizationInfo() 方法:用来为当前登陆成功的用户授予权限和角色

public class MyRealm extends AuthorizingRealm {
    @Resource
    UserService userService;

    @Override
    //用来为当前登陆成功的用户授予权限和角色
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }

    @Override
    //用来验证当前登录的用户,获取认证信息
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       
            return null;
       

    }

}

后续实现具体功能再对这两个方法进行具体实现和讲解;

3.shiro配置文件

1.ShiroFileterFactoryBean : 可以进行权限设置,访问控制等功能,由DefaultWebSecurityManager 对设置进行实现;

2.DefaultWebSecurityManager :根据自定义的Realm进行安全管理;

3.自定义Realm: 自定义验证规则;

@Configuration
public class ShiroConfig {
/*
1.ShiroFileterFactoryBean
* */
@Bean
public ShiroFilterFactoryBean getshShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    //添加shiro内置过滤器
    /*
    * 常用过滤器:
    *   anon:无需认证(登录)可以访问
    *   authc:必须认证才可以访问
    *   user: 如果使用remmemberMe的功能可以直接访问
    *   perms: 该资源必须得到资源权限才可以访问
    *   roles:该资源必须得到角色权限才可以访问
    * */

    //配置过滤规则
    Map<String,String> filter=new LinkedHashMap<>();
    filter.put("/user/add","anon");
    filter.put("/user/update","authc");
    //放行hello.html
    filter.put("/hello.html","anon");
    //拦截所有请求包括html也会被拦截
    filter.put("/*","authc");
    //将过滤规则设置到shiroFilterFactoryBean
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filter);
    //访问被拦截后访问的地址,如/user/update被拦截时就会访问/user/toLogin;
    shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    return shiroFilterFactoryBean;
}

/*
* 2.DefaultWebSecurityManager
* */
@Bean(name = "securityManager")
public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("MyRealm")Realm myRealm) {
    DefaultSecurityManager securityManager = new DefaultWebSecurityManager(myRealm);
    return securityManager;
}

/*
* 3.自定义Realm
* */
@Bean(name = "MyRealm")
public Realm getRealm() {
    return new MyRealm();
}
}

配置的过滤规则只有访问controller层进行跳转时才能生效,直接访问xxx.html页面是不生效的,也就是说即使你配置了拦截/add,也可以访问add.html;

4.2 登入验证

1.创建数据库表

暂时只用到用户表,角色表和权限表用于后续授权;

#角色表
CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `rolename` varchar(20) DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

#用户表
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键', `username` varchar(20) NOT NULL COMMENT '用户名', `password` varchar(20) NOT NULL COMMENT '密码' ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8


#权限表
CREATE TABLE `t_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `permissionname` varchar(50) NOT NULL COMMENT '权限名', `role_id` int(11) DEFAULT NULL COMMENT '外键关联role', PRIMARY KEY (`id`), KEY `role_id` (`role_id`), CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

#用户与权限表
CREATE TABLE `user_role` (
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.自定义Realm的doGetAuthenticationInfo方法添加具体实现

public class MyRealm extends AuthorizingRealm {
    @Resource
    UserService userService;

    @Override
    //用来为当前登陆成功的用户授予权限和角色
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }

    @Override
    //用来验证当前登录的用户,获取认证信息
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 根据token获取用户名,此Token是controller层穿来的,具体看下一步
        String userName = (String) authenticationToken.getPrincipal();
        // 根据用户名从数据库中查询该用户
        User user = userService.getByUsername(userName);
        if (user != null) {
     //返回SimpleAuthenticationInfo来验证密码,只有user.getPassword()为必填参数,其他的可以填“”;         //验证失败 subject.login(token);方法会出现IncorrectCredentialsException异常      
            return SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "MyRealm");
        } else {
            //返回null subject.login(token)方法会报UnknownAccountException异常;
            return null;
        }

    }

}

3.编写controller

subject.login(token)这一步会带着存有前端传来的用户信息去自定义的Realm中的doGetAuthenticationInfo验证用户信息;

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public String login(User user, Model model) {
        if (user == null) {
            return "user/login";
        } else {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword());
            try {
                subject.login(token);
            } catch (UnknownAccountException e) {
                model.addAttribute("msg", "用户不存在");
                return "user/login";
            } catch (IncorrectCredentialsException e) {
                model.addAttribute("msg","密码错误");
               return  "user/login";
            }
        }
        return "success";
    }
}

4.前端代码login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p style="color: red" th:text="${msg}"></p>
<form action="/user/login" >
    <p>请输入userName:</p>
    <input type="text" name="userName">
    <p>请输入password:</p>
    <input type="text" name="password" >
    <input type="submit" value="submit">
</form>
</body>
</html>

5.测试

输入不存在用户时:
Spring Boot中集成Shiro

输入错误密码时:

Spring Boot中集成Shiro

输入正确密码:

Spring Boot中集成Shiro

登录验证成功后 shiroConfig中getshShiroFilterFactoryBean方法所配置的需要验证才能访问的请求(authc)就都能访问了;

4.3 添加角色和添加授权

数据库表结构:

t_user:

“id” “username” “password”
“1” “yg1” “a”
“2” “yg2” “a”
“3” “yg3” “a”

t_role

“id” “rolename”
“1” “admin”
“2” “boss”
“3” “user”

t_permission

“id” “permissionname” “role_id”
“1” “user:*” “1”
“2” “user:add” “2”
“3” “user:update” “3”

user_role

“u_id” “r_id”
“1” “1”
“2” “2”
“3” “3”

1.userMapper接口的编写

public interface UserMapper {
    public Set<Role> getRoles(String userName);
    public Set<Permission> getPermissions(String userName);
    public User getByUsername(String userName);
}

2.userMapper.xml的编写

<!--     public User getByUsername(String userName);-->
    <select id="getByUsername" resultType="User">
   select * from t_user where username=#{userName}
    </select>

    <!--     public Set<Role> getRoles(String userName);-->
    <select id="getRoles" resultType="Role">
    SELECT tr.* from t_role tr,user_role ur where
    tr.id=ur.r_id and ur.u_id in (select id from
   t_user where username=#{userName})
    </select>

    <!--    public Set<Permission> getPermissions(String userName);-->
    <select id="getPermissions" resultType="Permission">
SELECT tp.* from t_permission tp,user_role ur
  where tp.role_id=ur.r_id AND ur.u_id in (select id
	 from t_user where username=#{userName})
    </select>

3.userService编写

@Service
public  class UserService {

    @Resource
    UserMapper userMapper;
    public Set<Role> getRoles(String userName) {
        if (userName == "" || userName == null) {
            return null;
        }
        Set<Role> roleList = userMapper.getRoles(userName);
        return roleList;
    }


    public Set<Permission>getPermissions(String userName) {
        if (userName == "" || userName == null) {
            return null;
        }
        Set<Permission> permissions = userMapper.getPermissions(userName);
        return permissions;
    }
    public User getByUsername(String userName) {
        if (userName == "" || userName == null) {
            return null;
        }
        return userMapper.getByUsername(userName);
    }
}

4.自定义Realm

AuthorizationInfo方法中添加角色和添加授权;

  @Override
    //用来为当前登陆成功的用户授予权限和角色
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权操作");
                User user = (User) principalCollection.getPrimaryPrincipal();
                String userName=user.getUserName();
       SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //添加角色
       Set<Role> roles = userService.getRoles(userName);
        //将roles转为set<String>roleNames
        Set<String>roleNames=new HashSet<>();
        for (Role role : roles) {
          roleNames.add(role.getRoleName());
        }
        authorizationInfo.setRoles(roleNames);

        //添加授权
        Set<Permission> permissionSet = userService.getPermissions(userName);
        //将roles转为set<String>roleNames
        Set<String>permissions=new HashSet<>();
        for (Permission permission : permissionSet) {
            permissions.add(permission.getPermissionName());
        }
        authorizationInfo.setStringPermissions(permissions);


        return authorizationInfo;
    }

5.shiroConfig配置文件

在shiroConfig的ShiroFilterFactoryBean方法中添加角色认证和权限认证;

filter.put("/user/add",“perms[user:add]”);

filter.put("/user/update",“roles[admin,boss]”);

@Bean
public ShiroFilterFactoryBean getshShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //添加shiro内置过滤器
    /*
    * 常用过滤器:
    *   anon:无需认证(登录)可以访问
    *   authc:必须认证才可以访问
    *   user: 如果使用remmemberMe的功能可以直接访问
    *   perms: 该资源必须得到资源权限才可以访问
    *   roles:该资源必须得到角色权限才可以访问
    * */

    Map<String,String> filter=new LinkedHashMap<>();
    //filter.put("/user/add","anon");
    //filter.put("/user/update","authc");

    //授权过滤器
    filter.put("/user/add","perms[user:add]");
    filter.put("/user/update","roles[user,boss]");
    //未授权就跳转到该页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/user/unAuth");
    //拦截所有请求包括html也会被拦截
   // filter.put("/*","authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filter);
    shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    return shiroFilterFactoryBean;
}

测试:

yg1有user:权限,yg2中有user:add权限所以可以访问*/user/add**,yg3没有这些权限不能访问,yg1没有被授权user或boss角色所以不能访问/user/update而yg2被授权boss,yg3被授权user所以能访问;

5 thymeleaf整合shiro

1.导入依赖
 <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
2.shiroConfig配置类添加bean
   @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

3.编写前端代码

xmlns:shiro=“http://www.pollix.at/thymeleaf/shiro” 导入后才能有shiro标签提示;

**xmlns:th=“http://www.thymeleaf.org” ** 导入后才能有thymeleaf标签提示;

shirol:标签后面的条件成立才会显示该标签的内容

常用shirol:标签解释

shiro:guest="" 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。

<p shiro:guest="">Please <a href="login.html">login</a></p>

shiro:user="" 认证通过或已记住的用户

<p shiro:user="">Welcome back </p>

shiro:authenticated="" 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在

<a shiro:authenticated="" href="update.html">Update your  information</a>

shiro:principal 输出当前用户信息,通常为登录帐号信息。

<p>Hello, <shiro:principal/>, how are you today?</p>

shiro:notAuthenticated="" 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。

shiro:hasRole=“xxx” 验证当前用户是否属于xxx角色

<a shiro:hasRole="admin" href="admin.html">Administer the system</a><!-- 拥有该角色 -->

shiro:lacksRole=“xxx” 与hasRole标签逻辑相反,当用户不属于该角色时验证通过

shiro:hasAllRoles=“xx1, xx2” 验证当前用户是否属于以下所有角色

shiro:hasAnyRoles="xx1, xx2 " 验证当前用户是否属于以下任意一个角色

shiro:hasPermission ="" 与Role的使用相同也有对应的lacksPermission等;

<!DOCTYPE html>
<html lang="en"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<div shiro:hasPermission="user:update">
<a href="/user/update">更新:</a>
</div>

<div shiro:hasPermission="user:add">
    <a href="add">添加:</a>
</div>

</body>
</html>

测试结果:

*yg1有user:权限所以都能显示,yg2只有user:add权限只能显示添加,yg2只有user:update权限只能显示更新;

只有经过controller层进行模板解析后跳转的html页面shiro标签才会起效;

相关标签: springBoot整合