Shiro-JWT学习_用户名

Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。 使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的web和企业应用程序。

Shiro和JWt

课程地址:【【编程不良人】2020最新版Shiro教程,整合SpringBoot项目实战教程】

一、Shiro

1.1、权限管理

什么是权限管理

权限管理包括用户身份认证和授权两部分,简称认证授权。对于
需要访问控制的资源用户首先经过身份认证,认证通过后用户
具有该资源的访问权限方可访问

什么是身份认证:Authenticator

身份认证,就是判断一个用户是否为合法用户的处理过程。
最常用的简单身份认证方式是系统通过核对用户输入的用
户名和口令,看其是否与系统中存储的该用户的用户名和
口令一致,来判断用户身份是否正确。对于采用指纹等
系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

什么是授权:Authorizer

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认
证后需要分配权限方可访问系统的资源,对于某些资源没有
权限是无法访问的

1.2、Shiro核心架构

Shiro-JWT学习_apache_02

Subject

Subject即主体,外部应用与subject进行交互,subject记录了当前
操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏
览器请求的用户,也可能是一个运行的程序。    Subject在shiro中
是一个接口,接口中定义了很多认证授相关的方法,外部程序通
过subject进行认证授,而subject是通过SecurityManager安全
管理器进行认证授权

SecurityManager

SecurityManager即安全管理器,对全部的subject进行安全管理,
它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等.
实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer,SessionManager这三个接口。

Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口.
shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

Authorizer

Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

Realm

Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据.
比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

SessionManager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session.
所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库

CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能

Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能

1.3、Shiro中的认证 

1、创建maven项目,引入依赖

org.apache.shiro
    shiro-core
    1.11.0

2、引入shiro配置文件并加入如下配置:shiro.ini

[users]
xiaochen=123
zhangsan=456

3、认证代码

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

public class TestAuthenticator {
    public static void main(String[] args) {

        //1.创建安全管理器对象
        DefaultSecurityManager securityManager =  new DefaultSecurityManager();
        //2.给安全管理器设置realm
        securityManager.setRealm( new IniRealm( "classpath:shiro.ini" ));
        //3.SecurityUtils 给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4.关键对象 subject 主体
        Subject subject = SecurityUtils.getSubject();
        //5.创建令牌
        UsernamePasswordToken token =  new UsernamePasswordToken( "xiaochen1" ,  "123" );

        try {
            System. out .println( "认证状态: " + subject.isAuthenticated());
            //用户认证
            subject.login(token);
            System. out .println( "认证状态: " + subject.isAuthenticated());
        } catch (UnknownAccountException e){
            e.printStackTrace();
            System. out .println( "认证失败: 用户名不存在~" );
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System. out .println( "认证失败: 密码错误~" );
        }

    }
}

4、异常

DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等

1.3.1、自定义Realm

上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。

Shiro提供的Realm

Shiro-JWT学习_Shiro_03

自定义Realm实现AuthorizingRealm:源码

Shiro-JWT学习_Shiro_04

public class SimpleAccountRealm extends AuthorizingRealm {
        //.......省略
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        SimpleAccount account = getUser(upToken.getUsername());

        if (account !=  null ) {

            if (account.isLocked()) {
                throw new LockedAccountException( "Account [" + account +  "] is locked." );
            }
            if (account.isCredentialsExpired()) {
                String msg =  "The credentials for account [" + account +  "] are expired" ;
                throw new ExpiredCredentialsException(msg);
            }

        }

        return account;
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = getUsername(principals);
        USERS_LOCK.readLock(). lock ();
        try {
            return this .users. get (username);
        }  finally {
            USERS_LOCK.readLock().unlock();
        }
    }
}

自定义Realm

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @ClassName ConsumerRealm
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-12 22:17
 * @Version 1.0
 */
public class ConsumerRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null ;
    }

    /**
     * 认证
     *
     * @param token 用户名密码token
     * @return 认证实例
     * @throws AuthenticationException 身份验证异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 通过token获取用户名
        String userName = token.getPrincipal().toString();
        // 这里模拟从数据库中验证用户,之后可能从JDBC、Mybatis取
        if ( "xiaochen" . equals (userName)) {
            // 参数1:返回数据库中正确的用户名 参数2:返回数据库中正确密码 参数3:提供当前realm的名字 this.getName();
            return new SimpleAuthenticationInfo(userName,  "1234" ,  this .getName());
        }
        return null ;
    }
}

测试  

public class TestConsumerRealm {
    public static void main(String[] args) {

        //创建securityManager
        DefaultSecurityManager defaultSecurityManager =  new DefaultSecurityManager();
        //设置自定义realm
        defaultSecurityManager.setRealm( new ConsumerRealm());
        //将安全工具类设置安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //创建token
        UsernamePasswordToken token =  new UsernamePasswordToken( "xiaochen" ,  "123" );
        //执行登录
        try {
            subject.login(token);
        }  catch (UnknownAccountException unknownAccountException) {
            unknownAccountException.printStackTrace();
            System. out .println( "用户名验证错误" );
        }  catch (IncorrectCredentialsException incorrectCredentialsException) {
            incorrectCredentialsException.printStackTrace();
            System. out .println( "密码验证错误" );
        }

    }
}

1.3.2、使用MD5+盐(Salt),实现认证

1、创建用户名和密码时候使用的加盐规则

import org.apache.shiro.crypto.hash.Md5Hash;

/**
 * @ClassName ShiroMd5Utils
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-12 23:13
 * @Version 1.0
 */
public class ShiroMd5Utils {
    public static void main(String[] args) {
        // 使用用户名做盐值,为了让盐是动态的
        String userName =  "xiaochen" ;

        //使用md5
        Md5Hash md5Hash =  new Md5Hash( "123" );
        System. out .println(md5Hash.toHex());

        //使用MD5 + salt处理
        Md5Hash md5Hash1 =  new Md5Hash( "123" , userName);
        System. out .println(md5Hash1.toHex());

        //使用md5 + salt + hash散列
        Md5Hash md5Hash2 =  new Md5Hash( "123" , userName, 1024);
        System. out .println(md5Hash2.toHex());
    }
}

2、设置自定义:MD5+盐,的认证代码

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * 使用自定义realm 加入md5 + salt +hash
 */
public class CustomerMd5Realm extends AuthorizingRealm {

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null ;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = token.getPrincipal().toString();

        //根据用户名查询数据库
        if ( "xiaochen" . equals (principal)) {
            // 参数1: 数据库用户名
            // 参数2:数据库md5+salt之后的密码
            // 参数3:注册时的随机盐,本例子中使用动态的,用户名作为随机盐
            // 参数4:realm的名字
            return new SimpleAuthenticationInfo(
                    principal,
                    "847b2f7a3705be61f6738b48bed195f8" ,
                    ByteSource.Util.bytes(principal),
                    this .getName());
        }
        return null ;
    }
}

3、测试

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestCustomerMd5RealmAuthenicator {

    public static void main(String[] args) {

        // 1、创建安全管理器
        DefaultSecurityManager defaultSecurityManager =  new DefaultSecurityManager();

        // 2、注入realm
        CustomerMd5Realm realm =  new CustomerMd5Realm();
        // 3、设置realm使用hash凭证匹配器
        HashedCredentialsMatcher credentialsMatcher =  new HashedCredentialsMatcher();
        // 使用算法
        credentialsMatcher.setHashAlgorithmName( "md5" );
        // 散列次数
        credentialsMatcher.setHashIterations(1024);
        // 4、设置凭据匹配器
        realm.setCredentialsMatcher(credentialsMatcher);

        defaultSecurityManager.setRealm(realm);
        // 5、将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // 6、通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();

        // 7、认证
        UsernamePasswordToken token =  new UsernamePasswordToken( "xiaochen" ,  "123" );
        try {
            subject.login(token);
            System. out .println( "登录成功" );
        }  catch (UnknownAccountException e) {
            e.printStackTrace();
            System. out .println( "用户名错误" );
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System. out .println( "密码错误" );
        }

    }
}

1.4、Shiro中的授权

  授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

1.4.1、关键对象

Who,即主体(Subject),主体需要访问系统中的资源。

What,即资源(Resource),如系统菜单、页面、按钮、类方法、
系统商品信息等。资源包括资源类型和资源实例,比如商品信息
为资源类型,类型为t01的商品为资源实例,编号为001的商品信息
也属于资源实例。

How,权限/许可(Permission),规定了主体对资源的操作许可,
权限离开资源没有意义,如用户查询权限、用户添加权限、某个
类方法的调用权限、编号为001用户的修改权限等,通过权限可
知主体对哪些资源都有哪些操作许可。

1.4.2、授权流程

Shiro-JWT学习_JWT_05

1.4.3、授权方式

基于角色的访问控制RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制

if (subject.hasRole( "admin" )){
   //操作什么资源
}

基于资源的访问控制RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制

if (subject.isPermission( "user:update:01" )){  //资源实例
//对01用户进行修改
}
if (subject.isPermission( "user:update:*" )){   //资源类型
//对01用户进行修改
}

1.4.4、权限字符串

权限字符串的规则是:

  资源标识符:操作:资源实例标识符

意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。

例子:
  用户创建权限:user:create,或user:create:*
  用户修改实例001的权限:user:update:001
  用户实例001的所有权限:user:*:001

1.4.5、自定义Realm重写授权

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.Arrays;

/**
 * 使用自定义realm 加入md5 + salt +hash
 */
public class CustomerMd5Realm extends AuthorizingRealm {


    /**
     * 授权
     * @param principals subject实体
     * @return 授权对象
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 1、获取当前身份信息
        String userName = principals.getPrimaryPrincipal().toString();

        // 2、获取权限对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 3、添加角色(从数据库中获取)
        authorizationInfo.addRoles(Arrays.asList("admin", "user"));
        // 4、添加权限:userName这个用户在user模块具有所有权限
        authorizationInfo.addStringPermissions(Arrays.asList("user:*:" + userName));
        return authorizationInfo;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = token.getPrincipal().toString();

        //根据用户名查询数据库
        if ("xiaochen".equals(principal)) {
            // 参数1: 数据库用户名
            // 参数2:数据库md5+salt之后的密码
            // 参数3:注册时的随机盐,本例子中使用动态的,用户名作为随机盐
            // 参数4:realm的名字
            return new SimpleAuthenticationInfo(
                    principal,
                    "847b2f7a3705be61f6738b48bed195f8",
                    ByteSource.Util.bytes(principal),
                    this.getName());
        }
        return null;
    }
}

1.4.6、测试授权

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

import java.util.Arrays;

public class TestCustomerMd5RealmAuthenicator {

    public static void main(String[] args) {

        // 1、创建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        // 2、注入realm
        CustomerMd5Realm realm = new CustomerMd5Realm();
        // 3、设置realm使用hash凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 使用算法
        credentialsMatcher.setHashAlgorithmName("md5");
        // 散列次数
        credentialsMatcher.setHashIterations(1024);
        // 4、设置凭据匹配器
        realm.setCredentialsMatcher(credentialsMatcher);

        defaultSecurityManager.setRealm(realm);
        // 5、将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // 6、通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();

        // 7、认证
        UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
        try {
            subject.login(token);
            System.out.println("登录成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }

        // 判断是否已经认证
        if (subject.isAuthenticated()) {
            System.out.println("************基于角色的控制************");
            // 基于角色的权限控制,是否有admin角色
            System.out.println(subject.hasRole("admin"));
            // 基于角色的权限控制,是否有其中的一种角色信息
            System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("admin", "super"))));
            // 基于角色的权限控制,是否同时具有提供的所有权限
            System.out.println(subject.hasAllRoles(Arrays.asList("admin", "super")));
            try {
                System.out.println("************基于权限的控制************");
                // 资源标识符:操作:资源类型,比如当前用户是否对user模块具有所有权限就可以这样写(user:*:xiaochen)
                System.out.println(subject.isPermitted("user:*:xiaochen"));
                // 分别具有哪些权限
                System.out.println(Arrays.toString(subject.isPermitted("user:*:xiaochen", "user:update:xiaochen")));
                // 同时具有哪些权限
                System.out.println(subject.isPermittedAll("user:*:xiaochen", "commodity:update:xiaochen"));
            } catch (AuthorizationException e) {
                e.printStackTrace();
                System.out.println("未经授权,无法访问");
            }
        }
    }
}

Shiro-JWT学习_ci_06

 1.5、常见过滤器

配置缩写

对应的过滤器

功能

anon

AnonymousFilter

指定url可以匿名访问

authc

FormAuthenticationFilter

指定url需要form表单登录,默认会从请求中获取usernamepassword,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。

authcBasic

BasicHttpAuthenticationFilter

指定url需要basic登录

logout

LogoutFilter

登出过滤器,配置指定url就可以实现退出功能,非常方便

noSessionCreation

NoSessionCreationFilter

禁止创建会话

perms

PermissionsAuthorizationFilter

需要指定权限才能访问

port

PortFilter

需要指定端口才能访问

rest

HttpMethodPermissionFilter

将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释

roles

RolesAuthorizationFilter

需要指定角色才能访问

ssl

SslFilter

需要https请求才能访问

user

UserFilter

需要已登录或“记住我”的用户才能访问

 1.6、Shiro整合SpringBoot(JSP)

Shiro-JWT学习_Shiro_07

1.6.1、引入依赖

org.apache.tomcat.embed
    tomcat-embed-jasper


    javax.servlet.jsp.jstl
    jstl-api
    1.2


    org.apache.shiro
    shiro-spring-boot-starter
    1.5.3

1.6.2、配置项目

server.servlet.context-path=/
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

1.6.3、编写jsp页面

├─resources
│  │  application.properties
│  │  rebel.xml
│  │
│  ├─ static
│  └─templates
└─webapp
        index.jsp  -- 受限资源
        login.jsp -- 公共资源

1.6.4、自定义Realm

public class CustomerRealm extends AuthorizingRealm {
    //处理授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null ;
    }

    //处理认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
            AuthenticationException {
        return null ;
    }
}

1.6.5、配置Shiro

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
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.HashMap;
import java.util.Map;

/**
 * @ClassName ShiroConfig
 * @Author zhangzhixi
 * @Description
 * @Date 2023-03-15 16:35
 * @Version 1.0
 */
@Configuration
public class ShiroConfig {

    /**
     * shiro过滤器工厂
     * @param securityManager shiro安全管理器
     * @return 过滤器
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier( "securityManager" ) SecurityManager securityManager) {
        // 1、创建shiro的filter
        ShiroFilterFactoryBean shiroFilterFactoryBean =  new ShiroFilterFactoryBean();
        // 2、注入安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<string, string=""> authMap =  new HashMap<>();
        // 配置公共资源

        // 配置受限资源
        authMap.put( "/index.jsp" ,  "authc" );
        // 如果是受限资源,默认会重定向login.jsp
        //shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
        return shiroFilterFactoryBean;
    }

    /**
     * shiro的安全管理器
     * @param realm realm
     * @return shiro的安全管理器
     */
    @Bean( "securityManager" )
    public DefaultWebSecurityManager getSecurityManager(@Qualifier( "realm" ) Realm realm) {
        DefaultWebSecurityManager defaultWebSecurityManager =  new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    /**
     * 创建自定义realm
     * @return 自定义Realm
     */
    @Bean( "realm" )
    public Realm getRealm() {
        return new CustomerRealm();
    }
}</string,>

1.6.6、测试  

Shiro-JWT学习_Shiro_08

1.6.7、认证与退出登录

登录表单:login.jsp

<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
    <label>
        用户名:
        <input type="text" name="username">
    </label> <br/>
    <label>
        密码 :
        <input type="text" name="password">
    </label> <br>
    <input type="submit" value="登录">
</form>

受限页面:index.jsp

<h2>系统主页v1.0(受限页面)</h2>
<label>
    <a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<ul>
    <li>用户管理</li>
    <li>商品管理</li>
    <li>订单管理</li>
    <li>物流管理</li>
</ul>
</body>
</html>

配置拦截路径:ShiroConfig

/**
     * shiro过滤器工厂
     * @param securityManager shiro安全管理器
     * @return 过滤器
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        // 1、创建shiro的filter
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 2、注入安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, String> authMap = new HashMap<>();
        // 配置公共资源
        authMap.put("/login.jsp", "anon");
        // 配置受限资源
        authMap.put("/index.jsp", "authc");
        authMap.put("/", "authc");
        // 如果是受限资源,默认会重定向login.jsp
        //shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
        return shiroFilterFactoryBean;
    }

自定义Realm处理认证:ConsumerRealm

public class CustomerRealm extends AuthorizingRealm {
    //处理授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //处理认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("==========================");
        String principal = (String) token.getPrincipal();
        // 后序这里的用户名密码,从数据库中查
        if ("xiaochen".equals(principal)) {
            return new SimpleAuthenticationInfo(principal, "123", this.getName());
        }
        return null;
    }
}

web请求:UserController  

import org.apache.shiro.SecurityUtils;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;


@Controller
@RequestMapping("user")
public class UserController {
    /**
     * 用来处理身份认证
     *
     * @param username 用户名
     * @param password 密码
     * @return 登录成功返回页面
     */
    @RequestMapping(value = "login",method = RequestMethod.POST)
    public String login(String username, String password) {
        //获取主体对象
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误!");
        }
        return "redirect:/login.jsp";
    }

    /**
     * 退出系统
     */
    @GetMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.jsp";
    }
}

Shiro-JWT学习_用户名_09

 

 

  

 

 

 

 

2.3、认证(MD5+Sale)(连接数据库,操作Mybatis)

1、注册页面

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>注册页面</h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
<!--指定实际发送的请求方式-->
<input type="hidden" name="_method" value="PUT">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<input type="submit" value="注册">
</form>
 
</body>
</html>

2、登录页面

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<input type="submit" value="登录">    <a href="/register.jsp">没有账号?点我注册</a>
</form>
 
</body>
</html>

3、Controller

@Autowired
private UserService userService;
 
/**
* 用来处理身份认证
*
* @return 登录成功返回页面
*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public String login(User user) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
}
return "redirect:/login.jsp";
}
 
/**
* 退出系统
*/
@GetMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
 
 
/**
* 用户注册
* @param user 用户实体
* @return 注册成功返回登录页面,注册失败返回注册页面
*/
@PutMapping("/register")
public String saveUser(User user) {
int count = userService.saveUser(user);
if (count == 1) {
return "redirect:/login.jsp";
}
return "redirect:/register.jsp";
}
 
@RequestMapping("save")
public String save() {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
// 基于角色权限控制
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
} else {
System.out.println("无权访问!");
}
//基于权限字符串判断是否有权限访问
subject.isPermitted("admin", "user");
return "redirect:/index.jsp";
}

4、mapper  

<insert id="saveUser" parameterType="com.zhixi.pojo.User" useGeneratedKeys="true">
insert into shiro.t_user(username, password, salt)
values (#{username}, #{password}, #{salt});
</insert>

5、service层,对密码字段进行md5+Sale+Hash

@Autowired
private UserMapper userMapper;
@Override
public int saveUser(User user) {
//处理业务调用dao
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
return userMapper.saveUser(user);
}

6、自定义Realm处理登录

/**
* 处理认证
* @param token 用户登录信息
* @return 身份验证信息
* @throws AuthenticationException 异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取当前登录的用户名
String principal = token.getPrincipal().toString();
// 查询数据库
User user = userService.selectUserByName(principal);
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}

7、Shiro配置:配置请求过滤以及自定义Realm使用到的加密算法以及散列次数

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
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.HashMap;
import java.util.Map;
 
/**
* @ClassName ShiroConfig
* @Author zhangzhixi
* @Description
* @Date 2023-03-15 16:35
* @Version 1.0
*/
@Configuration
public class ShiroConfig {
 
/**
* shiro过滤器工厂
* @param securityManager shiro安全管理器
* @return 过滤器
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
// 1、创建shiro的filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 2、注入安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
 
Map<String, String> authMap = new HashMap<>();
// 配置公共资源
authMap.put("/login.jsp", "anon");
authMap.put("/user/login", "anon");
authMap.put("/register.jsp", "anon");
authMap.put("/user/register", "anon");
// 配置受限资源
authMap.put("/**", "authc");
authMap.put("/index.jsp", "authc");
authMap.put("/", "authc");
// 如果是受限资源,默认会重定向login.jsp
//shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
return shiroFilterFactoryBean;
}
 
/**
* shiro的安全管理器
* @param realm realm
* @return shiro的安全管理器
*/
@Bean("securityManager")
public DefaultWebSecurityManager getSecurityManager(@Qualifier("realm") Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
 
/**
* 创建自定义realm
* @return 自定义Realm
*/
@Bean("realm")
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}

8、测试 

添加用户:

  ShiroConfig->register.jsp-->Controller->入库

用户登录:

  ShiroConfig-->login.jsp-->Controller-->自定义Realm处理认证

2.4、授权(不操作DB)

 2.4.1、Shiro中实现授权方式

// 编码式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
 
// 注解式
@RequiresRoles("admin")
public void hello() {
//有权限
}
 
// 标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
注意: Thymeleaf 中使用shiro需要额外集成!

2.4.2、实现授权-自定义realm

/**
* 处理授权
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名
String userName = principals.getPrimaryPrincipal().toString();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
if ("admin".equals(userName)) {
// 添加角色
simpleAuthorizationInfo.addRoles(Arrays.asList("admin", "user"));
// 添加资源
simpleAuthorizationInfo.addStringPermissions(Arrays.asList("user:*:*", "admin:*:*"));
}
return simpleAuthorizationInfo;
}

2.4.3、实现授权-页面授权方式

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
 
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>

登录后即可看到页面上面展示的列表数据。  

 2.4.4、实现授权-代码授权方式

@RequestMapping("save")
public String save(){
//获取主体对象
Subject subject = SecurityUtils.getSubject();
// 基于角色权限控制
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串判断是否有权限访问
subject.isPermitted("admin", "user");
return "redirect:/index.jsp";
}

2.4.5、实现授权-注解授权方式

@RequiresRoles 用来基于角色进行授权

@RequiresPermissions 用来基于权限进行授权

// 用来判断角色:具有 admin/user
@RequiresRoles(value = {"admin", "user"}, logical = Logical.OR)
// 用来判断权限字符串
@RequiresPermissions("user:update:01")
@RequestMapping("saveToPermissions")
public String saveToPermissions() {
return "redirect:/index.jsp";
}

2.5、授权-基于角色(连接数据库,操作Mybatis)

Shiro-JWT学习_apache_10

1、数据库表设计

  • Shiro-JWT学习_用户名_11

2、实体

@Data
public class User {
private Integer id;
private String username;
private String password;
private String salt;
/**
* 用户角色信息
*/
private List<Role> roles;
}
 
 
@Data
public class Role {
/**
* 主键
*/
private Integer id;
/**
* 角色名称
*/
private String name;
 
}
 
 
 
@Data
public class Perms {
private Integer id;
/**
* 权限字符串
*/
private String name;
 
/**
* 权限url
*/
private String url;
 
}

3、根据用户名查询角色信息  

/**
* 根据用户名查询角色信息
* @param username 用户名
* @return 用户角色集合
*/
List<User> findRolesByUsernameRoles(String username);


4、mapper文件

<resultMap id="userRoleMap" type="com.zhixi.pojo.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<collection property="roles" javaType="list" ofType="com.zhixi.pojo.Role">
<result column="roleid" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUsernameRoles" resultMap="userRoleMap">
SELECT t.id, t.username, ur.roleid, r.`name` rname
FROM shiro.t_user t
LEFT JOIN shiro.t_user_role ur ON t.id = ur.userid
LEFT JOIN shiro.t_role r ON r.id = ur.roleid
WHERE t.username = #{username}
</select>

5、修改自定义Realm中自定义授权代码 

import com.zhixi.pojo.User;
import com.zhixi.sevice.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.List;
import java.util.Optional;
 
@Component
public class CustomerRealm extends AuthorizingRealm {
 
@Autowired
UserService userService;
 
/**
* 处理授权
*
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名(从认证那里传过来的)
User userLogin = (User)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 查询登录用户权限集合
List<User> userList = userService.findRolesByUsernameRoles(userLogin.getUsername());
Optional.ofNullable(userList).ifPresent(users -> users.forEach(user -> user.getRoles().forEach(role -> simpleAuthorizationInfo.addRole(role.getName()))));
// 添加角色权限
System.out.println(userList);
return simpleAuthorizationInfo;
}
 
 
//处理认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("==========================");
// 获取当前登录的用户名
String principal = token.getPrincipal().toString();
// 查询数据库
User user = userService.selectUserByName(principal);
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
}

6、受限页面:index.jsp

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<%--获取当前登录的用户名--%>
<shiro:authenticated>
<%--填写的是放在认证,SimpleAuthenticationInfo中的user数据--%>
<h3>
当前登录用户是:<shiro:principal property="username"/>
</h3>
</shiro:authenticated>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
 
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>

7、测试  

Shiro-JWT学习_JWT_12

2.6、授权-基于权限(连接数据库,操作Mybatis)

1、DB

-- 权限表数据
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (1, 'user:*:*', NULL);
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (2, 'product:*:01', NULL);
INSERT INTO `shiro`.`t_pers` (`id`, `name`, `url`) VALUES (3, 'order:*:*', NULL);
-- 基于角色的权限,一个角色可以有多个权限
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (1, 1, 1);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (2, 1, 2);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (3, 1, 3);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (4, 2, 1);
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES (5, 3, 2);

 2、接口

/**
* 根据角色id查询权限集合
*
* @param id 角色ID
* @return 角色的权限集合
*/
List<Perms> findPermsByRoleId(Integer id);

3、 mapper

<resultMap id="roleToPerms" type="com.zhixi.pojo.Perms">
<result column="pers_name" property="name"/>
<result column="pers_url" property="url"/>
</resultMap>
<select id="findPermsByRoleId" resultMap="roleToPerms">
SELECT role.id,
role.name   as role_name,
pers.`name` as pers_name,
pers.url    as pers_url
FROM shiro.t_role role
LEFT JOIN shiro.t_role_perms rp ON role.id = rp.roleid
LEFT JOIN shiro.t_pers pers ON rp.permsid = pers.id
WHERE role.id = #{id}
</select>

4、自定义Realm-添加权限信息

/**
* 处理授权
*
* @param principals 权限数据
* @return 返回的授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录用户名(从认证那里传过来的)
User userLogin = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 查询登录用户权限集合
List<User> userList = userService.findRolesByUsernameRoles(userLogin.getUsername());
Optional.ofNullable(userList).
ifPresent(users -> users.forEach(user -> {
user.getRoles().forEach(role -> {
// 添加角色信息
simpleAuthorizationInfo.addRole(role.getName());
// 根据角色ID查询权限集合
List<Perms> permsByRoleId = userService.findPermsByRoleId(role.getId());
// 添加权限信息
Optional.ofNullable(permsByRoleId).ifPresent(perms -> perms.forEach(per -> simpleAuthorizationInfo.addStringPermission(per.getName())));
}
);
}));
// 添加角色权限
return simpleAuthorizationInfo;
}

5、受限页面:index.jsp

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h2>系统主页v1.0(受限页面)</h2>
<label>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
</label>
<%--获取当前登录的用户名--%>
<shiro:authenticated>
<%--填写的是放在认证,SimpleAuthenticationInfo中的user数据--%>
<h3>
当前登录用户是:<shiro:principal property="username"/>
</h3>
</shiro:authenticated>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理(满足user/admin权限)</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<hr>
 
<shiro:hasRole name="admin">
<li><a href="">用户管理(满足admin权限)</a>
<ul>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</shiro:hasRole>
</body>
</html>

6、测试

 使用xiaochen登录,xioachen具有admin角色,admin角色具有:user:*:* product:*:01 order:*:*权限

 使用zhangsan登录,zhangsan具有user、product角色。

  user角色具有:user:*:* 

  product角色具有:product:*:01 权限

Shiro-JWT学习_apache_13

回到顶部

三、Shiro整合Cache

3.1、整合Ehcache

Shiro-JWT学习_Shiro_14

引入依赖

<!--Shiro整合Ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>

修改自定义Realm 

/**
 * 创建自定义realm
 * @return 自定义Realm
 */
@Bean("realm")
public Realm getRealm(){
    CustomerRealm customerRealm = new CustomerRealm();
    //设置hashed凭证匹配器
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    //设置md5加密
    credentialsMatcher.setHashAlgorithmName("md5");
    //设置散列次数
    credentialsMatcher.setHashIterations(1024);
    customerRealm.setCredentialsMatcher(credentialsMatcher);
    //开启缓存管理
    customerRealm.setCacheManager(new EhCacheManager());
    customerRealm.setCachingEnabled(true);//开启全局缓存
    customerRealm.setAuthenticationCachingEnabled(true);//认证认证缓存
    customerRealm.setAuthenticationCacheName("authenticationCache");
    customerRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
    customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}

测试

  在第一次加载的时候会加载权限认证授权等数据,后序不会加载。

3.2、整合Redis

1、导入SpringBoot整合Redis依赖

<!--redis整合springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置application.properties

##################Redis配置#############
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=2

3、SpringBoot获取Bean工具类

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
/**
* @author zhixi
*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
 
private static ApplicationContext context;
 
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
 
 
/**
* 根据bean名字获取工厂中指定bean 对象
*
* @param beanName bean名称
* @return bean对象
*/
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}

4、创建Redis缓存管理器

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
 
/**
* @author zhixi
*/
public class RedisCacheManager implements CacheManager {
 
/**
* 缓存
*
* @param cacheName 认证或者是授权缓存的统一名称
* @param <K> k
* @param <V> v
* @return 自定义CacheManager
* @throws CacheException 异常
*/
 
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K, V>(cacheName);
}
}

5、Redis缓存实现类

/**
* 自定义redis缓存的实现
*
* @param <k> k
* @param <v> v
*/
@SuppressWarnings("all")
public class RedisCache<k, v> implements Cache<k, v> {
 
private String cacheName;
 
public RedisCache() {
}
 
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
 
@Override
public v get(k k) throws CacheException {
v v = (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
return v;
}
 
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key: " + k);
System.out.println("put value:" + v);
getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);
return null;
}
 
@Override
public v remove(k k) throws CacheException {
System.out.println("=============remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());
}
 
@Override
public void clear() throws CacheException {
System.out.println("=============clear==============");
getRedisTemplate().delete(this.cacheName);
}
 
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
 
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
 
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
 
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
 
/*设置key的序列化方式*/
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}

6、设置Shiro盐的序列化方式

  此处需要在认证的时候设置盐的序列化方式,不能够使用之前的ByteSource.Util了,否则会出现登录(序列化)正常,第二次登录(反序列化)失败的错误

Shiro-JWT学习_JWT_15

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.util.ByteSource;
 
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
 
/**
* 解决:
*  shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
*  no valid constructor
*  序列化后,无法反序列化的问题(https://blog.csdn.net/qq_15090197/article/details/108743271)
*/
public class MySimpleByteSource implements ByteSource, Serializable {
private static final long serialVersionUID = 1L;
 
private  byte[] bytes;
private String cachedHex;
private String cachedBase64;
 
public MySimpleByteSource(){
}
 
public MySimpleByteSource(byte[] bytes) {
this.bytes = bytes;
}
 
public MySimpleByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
 
public MySimpleByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
 
public MySimpleByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
 
public MySimpleByteSource(File file) {
this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(file);
}
 
public MySimpleByteSource(InputStream stream) {
this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(stream);
}
 
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
 
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
 
@Override
public byte[] getBytes() {
return this.bytes;
}
 
 
@Override
public String toHex() {
if(this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
 
@Override
public String toBase64() {
if(this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
 
return this.cachedBase64;
}
 
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toString() {
return this.toBase64();
}
 
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
}
 
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
 
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
 
public byte[] getBytes(File file) {
return this.toBytes(file);
}
 
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
 
}

7、设置自定义Realm的管理器为redis

Shiro-JWT学习_Shiro_16

8、测试  

Shiro-JWT学习_Shiro_17

回到顶部

 四、SpringBoot整合验证码功能(非shiro)

1、验证码工具类

 View Code

2、添加验证码请求以及修改登录请求

/**
* 用来处理身份认证
*
* @return 登录成功返回页面
*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public String login(User user, String code, HttpSession session) {
//比较验证码
String codes = session.getAttribute("code").toString();
try {
if (codes.equalsIgnoreCase(code)) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:/index.jsp";
}else{
throw new RuntimeException("验证码错误!");
}
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("验证码错误");
}
return "redirect:/login.jsp";
}

3、登录页面添加验证码标签

<%@page cnotallow="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
cnotallow="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" cnotallow="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录页面</h1>
<form actinotallow="${pageContext.request.contextPath}/user/login" method="post">
<label>
用户名:
<input type="text" name="username">
</label> <br/>
<label>
密码 :
<input type="text" name="password">
</label> <br>
<label>
输入验证码:<input type="text" name="code">
<img src="${pageContext.request.contextPath}/user/getImage" alt="验证码"/>
</label><br>
 
<input type="submit" value="登录">    <a href="/register.jsp">没有账号?点我注册</a>
</form>
 
</body>
</html>

4、Shiro放行验证码请求

Shiro-JWT学习_Shiro_18

5、测试

Shiro-JWT学习_apache_19

回到顶部

五、Shiro整合Thymeleaf

 https://github.com/zhangzhixi0305/springboot-study-shiro/tree/master/shiro-04-thymeleaf