shiro框架的使用

Shiro是Apache公司推出一个权限管理框架,其内部封装了项目中的认证、授权、加密、会话等逻辑操作,通过这个框架可以简化项目中权限控制逻辑代码的编写。

Shiro框架概要架构

Shiro框架中主要通过Subject,SecuirtyManager,Realm对象完成认证和授权业务。

其中:

Subject此对象负责提交用户身份、权限等信息。

SecuirtyManager负责完成认证、授权等核心业务。

Realm负责通过数据逻辑对象获取数据库中的数据。

Shiro框架基础配置实现

添加依赖(添加此依赖之前,如果之前添加了Shiro-spring依赖,则要替换掉)

         
            org.apache.shiro
            shiro-spring-boot-web-starter
            1.7.0
        

添加完依赖后启动会失败,还需做其它配置

1.创建一个Realm类型的实现类(基于此类通过dao访问数据库)

package com.cy.pj.sys.service.realm;

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 ShiroRealm extends AuthorizingRealm {
    //此方法负责获取授权信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //此方法负责获取并封装认证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

第二步 在项目启动类中添加Realm对象配置

@Bean
    public Realm realm(){
        return new ShiroRealm();
    }

第三步在启动类中定义过滤规则(哪些访问路径进行认证才可以访问)

@Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        LinkedHashMapmap=new LinkedHashMap<>();
        //设置允许匿名方法的资源路径(不需要登录即可访问)
        map.put("/bower_components/**","anon");//key是路径  anon对应shiro中的一个匿名过滤器
        map.put("/build/**","anon");
        map.put("/dist/**","anon");
        map.put("/plugins/**","anon");
        //设置认证以后才可以访问的资源(注意这里的顺序,匿名访问的要放在上面)
        map.put("/**","authc");//authc对应一个认证过滤器,表示认证以后才可以访问
        chainDefinition.addPathDefinitions(map);
        return chainDefinition;
    }

第四步配置认证页面yml文件中(登录页面)

shiro:
  loginUrl: /login.html

认证流程分析

shiro框架的使用_第1张图片

1) 系统调用subject的login方法将用户信息提交给SecurityManager
2) SecurityManager将认证操作委托给认证器对象Authenticator
3) Authenticator将用户输入的身份信息传递给Realm。
4) Realm访问数据库获取用户信息然后对信息进行封装并返回。
5) Authenticator 对realm返回的信息进行身份认证。

其中:

1) token:封装用户提交的认证信息(例如用户名和密码)的一个对象
2) Subject:负责将认证信息提交给SecurityManager对象的一个主体对象
3) SecurityManager:是Shiro框架的核心,负责完成其认证过程
4) Authenticator:认证管理器对象,SecurityManager继承了此接口
5) realm:负责从数据库获取认证信息提交给认证管理器


Shiro认证流程总结

1.登录客户端(login.html)中用户输入的登录信息提交SysUserController对象
2.SysUserController基于doLogin方法处理登录请求
3.SysUserController中doLogin方法将用户信息封装token中,然后基于subject对象将token提交给SecuirtyManager对象
4.SecuirtyManager对象调用认证方法(authenticate)去完成认证,在此方法内部会调用ShiroRealm中的doGetAuthenticationInfo获取数据库中的用户信息,然后再与客户端提交的token中的信息进行比对。比对时会调用getCredentialsMatcher方法获取凭证加密对象,通过此对象对用户提交的token中的密码进行加密。


Shiro框架认证业务实现:

1.通过Dao数据逻辑层基于用户名查询用户信息

SysUser findUserByUsername(String username);

1.1Mapper映射文件的sql语句

 

2.修改ShiroRealm中获取认证信息的方法

   @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取用户提交的认证信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        //基于用户名从数据库查询用户信息
        SysUser user = sysUserDao.findUserByUsername(username);
        //判断用户是否存在
        if (user == null) {
            throw new UnknownAccountException("用户不存在");//Shiro中的自定义异常
        }
        //判断用户是否被锁定
        if (user.getValid() == 0) {
            throw new LockedAccountException("账户被锁定");//Shiro中的自定义异常
        }
        //封装认证信息并返回
        ByteSource credentialsSalt=ByteSource.Util.bytes(user.getSalt());//对盐值的编码方式进行了处理
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(),credentialsSalt,"ShiroRealm");//传参内容:用户身份、已加密的密码、凭证盐、realm名字

        return simpleAuthenticationInfo;//返回给认证管理器
    }

3.在ShiroRealm中重写获取凭证加密算法的方法

   @Override//获取凭证匹配器的方法
    public CredentialsMatcher getCredentialsMatcher() {
        HashedCredentialsMatcher mather=new HashedCredentialsMatcher();
        mather.setHashAlgorithmName("MD5");//加密算法
        mather.setHashIterations(1);//加密次数
//        要和保存业务的时候设置的一致
        return mather;
    }

4.在SysUserController中添加处理登录请求的方法

 @PostMapping("doLogin")
    public JsonResult doLoginUI(String username,String password) {
        //将账号和密码封装token对象
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);//参考官网
        //基于subject对象将token提交给securityManager
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.login(token);
        return new JsonResult("login ok");
    }

5.在过滤配置中允许doLogin这个url匿名访问
map.put("/user/doLogin","anon");
6.在全局异常处理类中添加对于登录异常的处理方法

 @ExceptionHandler(ShiroException.class)
    public JsonResult doShiroException(ShiroException e) {
        JsonResult jsonResult = new JsonResult();
        jsonResult.setState(0);
        if (e instanceof UnknownAccountException) {
            jsonResult.setMessage("用户不存在");
        }else if (e instanceof IncorrectCredentialsException) {
            jsonResult.setMessage("密码不正确");
        } else if (e instanceof LockedAccountException) {
            jsonResult.setMessage("账户被锁定");
        } else if (e instanceof AuthorizationException) {
            jsonResult.setMessage("没有权限");
        } else {
            jsonResult.setMessage("认证或授权失败");
        }

        return jsonResult;
    }

7.在过滤配置中配置登出url操作

    map.put("/doLogout", "logout");//logout是Shiro框架给出的一个登出过滤器

Shiro框架授权业务的实现

在Dao中定义查询用户权限标识

 Set findUserPermission(Integer userId);

在Mapper中添加查询用户权限标识的SQL映射

   

修改ShiroRealm中获取权限并封装权限的信息的方法

 //此方法负责获取授权信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户id(登录时传入的用户身份是谁)
        SysUser user = (SysUser) principalCollection.getPrimaryPrincipal();
        //基于登录用户id获取用户权限标识
        Set userPermission = sysMenuDao.findUserPermission(user.getId());
        //封装数据并返回
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(userPermission);
        return simpleAuthorizationInfo;//返回给授权管理器
    }

授权原理分析(底层基于AOP实现):

1、页面上用户通过菜单触发对服务端资源的访问。
2、服务端Controller处理客户端的资源访问请求。
3、假如客户端请求访问的资源业务方法上有@RequiresPermissions注解描述则底层Controller对象会调用Service的代理对象,代理对象会调用AOP中通知方法,在通知方法中获取@RequiresPermissions("sys:user:update")上定义的权限标识。
4.通过Subject对象提交@RequiresPermissions注解中的授权标识给SecuirtyManager对象,此对象会调用ShiroRealm中的获取用户权限的方法,最终会将从数据库取到的权限信息与@RequiresPermissions中定义的权限信息做一个比对


页面呈现登录用户信息

//http://localhost/doIndexUI
    @GetMapping("doIndexUI")
    public String doIndexUI(Model model) {
        //获取登录用户信息(shiro框架给出的固定写法)
        SysUser user = (SysUser)SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("username", user.getUsername());
        return "starter";
    }

打开starter.html页面,找到用户名对应位置,然后通过[[${}]]表达式获取服务端model中数据,呈现在页面上

  

用户菜单的动态化呈现(不同用户登录后看到的菜单信息是不同的)

定义pojo对象存储用户菜单信息

package com.cy.pj.sys.pojo;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class SysUserMenu implements Serializable {
    private static final long serialVersionUID = 3542053307789523530L;
    private Integer id;
    private String name;
    private String url;
    private List childMenus;

}

在SysMenuDao中定义查询用户一级和二级菜单信息的方法

 ListfindUserMenus(Integer userId);

在SysMenuMapper中定义查询用户一级和二级菜单信息的对应SQL映射

 
    
        
        
        
        
            
            
            
        
    

修改PageController中doIndexUI方法

 //http://localhost/doIndexUI
    @GetMapping("doIndexUI")
    public String doIndexUI(Model model) {
        //获取登录用户信息(shiro框架给出的固定写法)
        SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("username", user.getUsername());
        List userMenus = sysMenuService.findUserMenus(user.getId());
        model.addAttribute("userMneus", userMenus);
        return "starter";
    }

修改starter页面菜单呈现部分的内容

  
  • [[${um.name}]]

  • Shiro框架中对密码的加密实现:

    首先是引入依赖

             
                org.apache.shiro
                shiro-spring
                1.7.0
            

    1.要拿到一个盐值

    String salt = UUID.randomUUID().toString();

    产生一个随机字符串作为加密盐使用

    2.对密码进行加密

     SimpleHash simpleHash = new SimpleHash("MD5",entity.getPassword(),salt,1);

    需要传入的参数,从左往右的顺序分别是:加密算法、对谁进行加密、加密使用的盐值是谁、加密次数

    3.把加密完的结果改为十六进制

    String hashedPassword = simpleHash.toHex();

    MD5加密算法的特点:
    1.不可逆;
    2.相同内容加密结果也相同,将密码存储到数据库的同时也会存储对应的盐值,存储盐值就是为了以后登录的时候再拿出来对比;例如:第一次设置完密码,加密完了的密文;下次登录的时候,会把登录时输入的密码再次加密,然后对比两个加密后的密文值,如果是一致的说明密码一致。

    @Test
        void testMD502() {
            String password="12345";
            String salt= UUID.randomUUID().toString();
            System.out.println(salt);//d5a3cfb5-b952-40cc-af62-fe79c76ad157
            SimpleHash simpleHash=new SimpleHash("MD5",password,salt,1);
            System.out.println(simpleHash.toHex());//9f23b14e61787294cf4dc5934e04f594
    
        }
        @Test
        void testMD503() {
            String password="12345";
    //        String salt= UUID.randomUUID().toString();
            String salt="d5a3cfb5-b952-40cc-af62-fe79c76ad157";
            SimpleHash simpleHash=new SimpleHash("MD5",password,salt,1);
            System.out.println(simpleHash.toHex());//9f23b14e61787294cf4dc5934e04f594
    
        }

    为什么要加盐值,是因为随机的字符串和密码放一起加密的安全性更高。

    你可能感兴趣的:(shiro框架的使用)