Shiro是如何进行权限管理

 

1、Shiro简介

1.1 什么是Shiro

  • Apache Shiro 是一个Java的安全框架

  • Shiro可以非常容易的开发出足够好的应用,其不仅可以使用在JavaSE环境,也可以用在JavaEE环境

  • Shiro可以完成,认证,授权,加密,会话管理,Web集成 ,缓存等方面

  • 下载地址:http://shiro.apache.org/download.html

 

1.2 有哪些功能

 

  • Authentication:身份认证、登陆、验证用户是不是拥有相应的身份

  • Authorization:授权,即权限验证,验证某个已认证的用户是不是拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户是否对某个资源有某个权限!

  • Session Manager:会话管理,即用户登陆后就是第一次会话,在没有退出之前,它的所有信息都在会话中,会话是可以普通的JAVA环境,也可以是Web环境

  • Cryptography:加密,保护数据的安全性,如密码存储到数据库中,而不是明文存储

  • Web Support:Web支持,可以非常容易的集成到Web环境

  • Caching:缓存,比如用户登陆后,用户信息,拥有的角色,权限不必每次去查,这样可以提高效率

  • Concurrency:Shiro支持多线程应用的并发验证,即:在一个线程中开启另一个线程,能把权限自动的传递过去

  • Testing:提供测试支持

  • Run As:允许一个用户假装成另一个用户的身份访问

  • Remember Me:记住我,这个非常常见的功能,即一次登陆后,下次再来的话可以不用登陆

1.3 Shiro架构

从外部来看Shiro,即应用程序角度来观察如何使用Shiro完成工作;

 

  • subject:应用代码直接交互的对象是Subject,也就是说Shiro对外API的核心就是Subject,Subject代表了当前的用户,这个用户不一定是具体的人,与当前应用交互的任何东西都是Subject,如:网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManager才是实际的执行者

  • SecurityManager:安全管理器,即所有安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,可以看出它是Shiro的核心,它对负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色

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

     

1.4 Shiro内部架构

 

  • Subject:任何可以与应用交互的”用户“

  • Subject Manager:相当于Spring MVC中的DispatcherServlet;是Shiro的心脏,所有具体交互管理都通过Security Manager进行控制,它的管理者所有的Subject,且负责进行权限认证,授权,会话,缓存的管理

  • Authenticator:负责Subject认证,是一个扩展点,可以自定义实现,可以使用认证策略(Authentication Strategy)即什么情况下都算用户通过验证

  • Authorizer:授权器,即访问控制器,用来决定主题是否拥有权限进行相关操作,即控制着用户访问应用中的那些功能

  • Realm:可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可以用JDBC实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的realm

  • SessionManager:管理Session生命周期dd饿组件,而Shiro并不仅仅是可以用在Web环境,也可以用在普通的JavaSE环境中

  • CacheManager:缓存控制器,来管理用户,角色,权限等缓存,因为这些数据基本很少改变,放到缓存后可以提高访问性能

  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于密码加密,解密等

 

2、HelloWorld

1、导入shiro需要的包

 
 
     org.apache.shiro
     shiro-spring
     1.5.3
 

2、快速开始shiro

 ​
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.*;
 import org.apache.shiro.config.IniSecurityManagerFactory;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.Factory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 ​
 public class Quickstart {
 ​
     //使用log来进行日志输出
     private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
 ​
 ​
     public static void main(String[] args) {
 ​
         // The easiest way to create a Shiro SecurityManager with configured
         // realms, users, roles and permissions is to use the simple INI config.
         // We'll do that by using a factory that can ingest a .ini file and
         // return a SecurityManager instance:
 ​
         // Use the shiro.ini file at the root of the classpath
         // (file: and url: prefixes load from files and urls respectively):
         //1、使用工厂模式来获得对象
         Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
         SecurityManager securityManager = factory.getInstance();
 ​
         SecurityUtils.setSecurityManager(securityManager);
 ​
 ​
         //2、三大对象之一:Subject
         // get the currently executing user
         // 可以通过方法获取当前用户
         Subject currentUser = SecurityUtils.getSubject();
 ​
         // Do some stuff with a Session (no need for a web or EJB container!!!)
         //通过当前对象拿到Session
         Session session = currentUser.getSession();
 ​
         session.setAttribute("someKey", "aValue");
         String value = (String) session.getAttribute("someKey");
         if (value.equals("aValue")) {
             log.info("Subject=>的session中someKey对应的值! [" + value + "]");
         }
 ​
         // let's login the current user so we can check against roles and permissions:
         //判断当前用户是否已经验证
         if (!currentUser.isAuthenticated()) {
             //如果已经验证,就拿到一个token
             //token:令牌,去ini文件里面判断是否拥有这个用户
             UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
             //设置记住我
             token.setRememberMe(true);
             try {
                 //执行了登录操作
                 currentUser.login(token);
             } catch (UnknownAccountException uae) {
                 //未知账户异常
                 log.info("There is no user with username of " + token.getPrincipal());
             } catch (IncorrectCredentialsException ice) {
                 //密码错误异常
                 log.info("Password for account " + token.getPrincipal() + " was incorrect!");
             } catch (LockedAccountException lae) {
                 //用户锁定异常
                 log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                         "Please contact your administrator to unlock it.");
             }
             // ... catch more exceptions here (maybe custom ones specific to your application?
             catch (AuthenticationException ae) {
                 //认证异常,上面的异常都是其子类
                 //unexpected condition?  error?
             }
         }
 ​
         //say who they are:
         //print their identifying principal (in this case, a username):
         //获取当前用户的一个认证码,用户的很多信息可以存储在这里
         log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
 ​
         //test a role:
         //测试用户是否由角色
         if (currentUser.hasRole("schwartz")) {
             log.info("May the Schwartz be with you!");
         } else {
             log.info("Hello, mere mortal.");
         }
 ​
         //test a typed permission (not instance-level)
         //测试用户的权限(粗粒度)
         //如果求权限不满足 就会报相应的错误
         if (currentUser.isPermitted("lightsaber:wield")) {
             log.info("You may use a lightsaber ring.  Use it wisely.");
         } else {
             log.info("Sorry, lightsaber rings are for schwartz masters only.");
         }
 ​
         //a (very powerful) Instance Level permission:
         //判断用户是否拥有更多权限(细粒度)
         if (currentUser.isPermitted("winnebago:drive:eagle5")) {
             log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                     "Here are the keys - have fun!");
         } else {
             log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
         }
 ​
         //all done - log out!
         //注销
         currentUser.logout();
 ​
         //结束
         System.exit(0);
     }
 }
 ​

3、shiro的配置文件shiro.ini

 [users]
 # user 'root' with password 'secret' and the 'admin' role
 root = secret, admin
 # user 'guest' with the password 'guest' and the 'guest' role
 guest = guest, guest
 # user 'presidentskroob' with password '12345' ("That's the same combination on
 # my luggage!!!" ;)), and role 'president'
 presidentskroob = 12345, president
 # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
 darkhelmet = ludicrousspeed, darklord, schwartz
 # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
 lonestarr = vespa, goodguy, schwartz
 ​
 # -----------------------------------------------------------------------------
 # Roles with assigned permissions
 #
 # Each line conforms to the format defined in the
 # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
 # -----------------------------------------------------------------------------
 [roles]
 # 'admin' role has all permissions, indicated by the wildcard '*'
 admin = *
 # The 'schwartz' role can do anything (*) with any lightsaber:
 schwartz = lightsaber:*
 # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
 # license plate 'eagle5' (instance specific id)
 goodguy = winnebago:drive:eagle5
 ​

4、log4fj的配置文件

 log4j.rootLogger=INFO, stdout
 ​
 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
 ​
 # General Apache libraries
 log4j.logger.org.apache=WARN
 ​
 # Spring
 log4j.logger.org.springframework=WARN
 ​
 # Default Shiro logging
 log4j.logger.org.apache.shiro=INFO
 ​
 # Disable verbose logging
 log4j.logger.org.apache.shiro.util.ThreadContext=WARN
 log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

 

3、SpringBoot集成Shiro

1、导入整合的包

 
 
     com.github.theborakompanioni
     thymeleaf-extras-shiro
     2.0.0
 
 ​
 ​
 
 
     org.projectlombok
     lombok
     1.18.8
     provided
 
 ​
 
 
     org.mybatis.spring.boot
     mybatis-spring-boot-starter
     2.1.0
 
 ​
 ​
 
 
     mysql
     mysql-connector-java
 
 
 
     com.alibaba
     druid
     1.1.12
 
 ​
 
 
     org.apache.shiro
     shiro-spring
     1.5.3
 
 ​
 
     org.springframework.boot
     spring-boot-starter-thymeleaf
 
 
     org.springframework.boot
     spring-boot-starter-web
 

2、shiro的配置类

 package com.wei.config;
 ​
 import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
 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.LinkedHashMap;
 import java.util.Map;
 ​
 @Configuration
 public class ShiroConfig {
     //三大要素
     //ShiroFilterFactoryBean:3
     @Bean
     public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
         ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
         //设置安全管理器
         bean.setSecurityManager(defaultWebSecurityManager);
         //添加shiro的内置过滤器
         /*
         anon:无需认证即可访问
         authc:必须认证才可访问
         user:必须拥有记住我功能才能用
         perms:拥有对某个资源的权限才能访问
         role:拥有某个角色权限才能访问
          */
         //登录拦截
         Map filterMap = new LinkedHashMap<>();
 ​
         //第一个参数是请求,第二个是设置的权限
         filterMap.put("/add","authc");
         filterMap.put("/update","authc");
 ​
         //授权
         //user用户拥有add权限才能访问 /add请求
         //正常情况下没有授权,会跳到授权页面
         filterMap.put("/add","perms[user:add]");
         filterMap.put("/update","perms[user:update]");
         bean.setFilterChainDefinitionMap(filterMap);
 ​
         //设置登录的请求
         bean.setLoginUrl("toLogin");
         //设置未授权页面
         bean.setUnauthorizedUrl("/noauth");
 ​
         return bean;
     }
 ​
 ​
     //DefaultWebSecurityManager:2
     @Bean(name = "webSecurityManager")
     public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
         DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
         //关联我们的userRealm
         webSecurityManager.setRealm(userRealm);
         return webSecurityManager;
     }
 ​
 ​
     //创建Realm对象,需要自定义 :1
     //将自己写的realm对象 交给spring容器托管
     @Bean
     public UserRealm userRealm(){
         return new UserRealm();
     }
 ​
     //整合ShiroDialect:Shiro方言  用来整合shiro-thymeleaf
     @Bean
     public ShiroDialect getShiroDialect(){
         return new ShiroDialect();
     }
 }
 ​

3、自定义领域(Realm)

 package com.wei.config;
 ​
 import com.wei.entity.User;
 import com.wei.service.UserService;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.*;
 import org.apache.shiro.authz.AuthorizationInfo;
 import org.apache.shiro.authz.SimpleAuthorizationInfo;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.Subject;
 import org.springframework.beans.factory.annotation.Autowired;
 ​
 //自定义的Realm UserRealm
 //要实现自定义的 只需要继承AuthorizingRealm 即可
 public class UserRealm extends AuthorizingRealm {
 ​
     @Autowired
     private UserService userService;
     //授权
     @Override
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
         System.out.println("执行了授权doGetAuthorizationInfo方法");
 ​
         //进入一些被拦截的页面 就会执行授权方法
         //给用户赋予权限的对象
         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
         //给用户添加权限
         //info.addStringPermission("user:add");
 ​
         //拿到当前登录的对象
         Subject subject = SecurityUtils.getSubject();
         User currentUser = (User) subject.getPrincipal();//这里取得是59行存得user得值
         //没有权限直接返回空  显示自己的没有权限界面
         if (currentUser.getPerms()==null){
             return null;
         }
 ​
         //设置当前用户的权限
         info.addStringPermission(currentUser.getPerms());
 ​
         //要返回赋予的权限对象
         return info;
     }
 ​
     //认证
     @Override
     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
         System.out.println("执行了认证doGetAuthenticationInfo方法");
         //伪造用户名和密码    真的要再数据库中去取
 //        String name = "root";
 //        String password = "123";
 ​
 ​
         //将认证的token转化成存储用户信息的token 方便进行信息的判断
         UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
         //连接真实数据库
         User user = userService.queryByName(token.getUsername());
 ​
         if (user==null){
             return null;//没有这个人报错
         }
 ​
         Subject currentUser = SecurityUtils.getSubject();
         Session session = currentUser.getSession();
         session.setAttribute("loginUser",user);
 ​
         //shiro也有一些加密方式:MD5  MD5盐值加密 SHA等 但是如果没有加密  密码正确一样可以访问
         //这个和SpringSecurity有一点区别
         //密码认证shiro来做,交给程序员操作可能会导致密码泄露
         return new SimpleAuthenticationInfo(user,user.getPwd(),"");
     }
 }
 ​

4、控制器

 package com.wei.controller;
 ​
 import com.sun.org.apache.regexp.internal.RE;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.IncorrectCredentialsException;
 import org.apache.shiro.authc.UnknownAccountException;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.subject.Subject;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 ​
 @Controller
 public class MyController {
     @RequestMapping({"/","/index"})
     public String toIndex(Model model){
         model.addAttribute("msg","hello,shiro");
         return "index";
     }
 ​
     @RequestMapping("/add")
     public String toAdd(){
         return "user/add";
     }
 ​
     @RequestMapping("/update")
     public String toUpdate(){
         return "user/update";
     }
 ​
 ​
     @RequestMapping("/toLogin")
     public String toLogin(){
         return "login";
     }
 ​
     @RequestMapping("/login")
     public String login(String username,String password,Model model){
         //获取当前的用户
         Subject subject = SecurityUtils.getSubject();
 ​
         //封装用户的登录数据  把账号密码放在一个令牌中加密
         UsernamePasswordToken token = new UsernamePasswordToken(username, password);
         //使用令牌登录,如果没有异常就说明ok
         try {
             subject.login(token);
             return "index";
         } catch (UnknownAccountException e) {
             model.addAttribute("msg","用户名错误");
             return "login";
         }catch (IncorrectCredentialsException e){
             model.addAttribute("msg","密码错误");
             return "login";
         }
     }
 ​
     @RequestMapping("/noauth")
     @ResponseBody
     public String noauth(){
         return "没有授权,无法登录";
     }
 ​
     @RequestMapping("/logout")
     public String logout(Subject subject){
         subject.logout();
         return "index";
     }
 }
 ​

5、实体类

 package com.wei.entity;
 ​
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 ​
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class User {
     private int id;
     private String name;
     private String pwd;
     private String perms;
 }
 ​

6、mapper接口

 package com.wei.mapper;
 ​
 import com.wei.entity.User;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.stereotype.Repository;
 ​
 @Repository
 @Mapper
 public interface UserMapper {
 ​
     public User queryByName(@Param("name") String name);
 }
 ​

实现类

 
 
 ​
 
 ​
     
 ​
 

7、Service

 package com.wei.service;
 ​
 import com.wei.entity.User;
 import org.apache.ibatis.annotations.Param;
 ​
 public interface UserService {
 ​
     public User queryByName(String name);
 }
 ​

实现类

 package com.wei.service;
 ​
 import com.wei.entity.User;
 import com.wei.mapper.UserMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 @Service
 public class UserServiceImpl implements UserService {
 ​
     @Autowired
     private UserMapper userMapper;
 ​
     @Override
     public User queryByName(String name) {
         User user = userMapper.queryByName(name);
         return user;
     }
 }
 ​

8、前端模板

login.html

 
 
 
     
     Title
 
 
 

登录页面

 
     

用户名:

     

密码:

     
       
 

   

index.html

 
 
 
     
     Title
 
 
 

    首页  

 

   
   
 
 
     登录  
 
     已登录      注销  
 ​    

add.html

 
 
 
     
     Title
 
 
 

    添加页面  

   

update.html




    
    Title



修改页面

9、Mybaties整合Springboot

#设置mybatis
mybatis.type-aliases-package=com.wei.entity
mybatis.mapper-locations=classpath:mapper/*.xml

10、数据源配置

spring:
  datasource:
    username: root
    password: root
    #serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

你可能感兴趣的:(后端笔记)