当前位置:   article > 正文

Shiro安全框架详解及springboot使用示例_springboot shiro

springboot shiro

目录

在这里插入图片描述

一、认识Shiro

1、什么是Shiro?
  • Apache Shiro 是一个Java 的安全(权限)框架。
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环
    境。
  • Shiro可以完成,认证授权加密会话管理Web集成缓存等。
2、有哪些功能?

在这里插入图片描述

  • Authentication:身份认证、登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否
    进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否
    具有某个权限!
  • Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都
    在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高
    效率
  • Concurrency:Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限
    自动的传播过去
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3、Shiro架构(外部)

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

在这里插入图片描述

  • Subject:代表当前"用户"。与当前应用程序交互的任何东西都是Subject,如爬虫、机器人。所有的Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager。Subject 是一个门面,SecurityManager 是实际的执行者。
  • SecurityManager:与安全有关的操作都会与SecurityManager交互。它管理者所有Subject,是Shiro的核心,负责与其他组件进行交互。
  • Realm: Shiro从Realm中获取安全数据(用户、角色、权限)。SecurityManager需要从Realm中获取响应的用户信息进行比较用户身份是否合法,也需要从Realm中得到用户相应的角色/权限进行验证,以确定用户是否能够操作。
4、Shiro架构(内部)

在这里插入图片描述

  • Subject:任何可以与应用交互的 用户;
  • Security Manager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏,所有具体的交互
    都通过Security Manager进行控制,它管理者所有的Subject,且负责进行认证,授权,会话,及
    缓存的管理。
  • Authenticator:负责Subject认证,是一个扩展点,可以自定义实现;可以使用认证策略
    (Authentication Strategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能
    访问应用中的那些功能;
  • Realm:可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可
    以用JDBC实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的realm
  • SessionManager:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用
    在普通的JavaSE环境中
  • CacheManager:缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改
    变,放到缓存中后可以提高访问的性能;
  • Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于密码加密,解密等

二、授权方式

5.1、三种授权方式

在这里插入图片描述

5.2、授权流程

在这里插入图片描述

流程如下:

  • 1、首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
  • 2、Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
  • 3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
  • 4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回 true,否则返回 false 表示授权失败。

ModularRealmAuthorizer 进行多 Realm 匹配流程:

  • 首先检查相应的 Realm 是否实现了实现了 Authorizer;
  • 如果实现了 Authorizer,那么接着调用其相应的 isPermitted*/hasRole* 接口进行匹配;
  • 如果有一个 Realm 匹配那么将返回 true,否则返回 false。

如果 Realm 进行授权的话,应该继承 AuthorizingRealm,其流程是:

  • 如果调用 hasRole*,则直接获取 AuthorizationInfo.getRoles() 与传入的角色比较即可;首先如果调用如 isPermitted(“user:view”),首先通过 PermissionResolver 将权限字符串转换成相应的 Permission 实例,默认使用 WildcardPermissionResolver,即转换为通配符的 WildcardPermission;
  • 通过 AuthorizationInfo.getObjectPermissions() 得到 Permission 实例集合;通过 AuthorizationInfo.getStringPermissions() 得到字符串集合并通过 PermissionResolver 解析为 Permission 实例;然后获取用户的角色,并通过 RolePermissionResolver 解析角色对应的权限集合(默认没有实现,可以自己提供);
  • 接着调用 Permission.implies(Permission p) 逐个与传入的权限比较,如果有匹配的则返回 true,否则 false。

三、认证方式

Realm:域

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

String getName(); //返回一个唯一的Realm名字
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
 throws AuthenticationException;  //根据Token获取认证信息
  • 1
  • 2
  • 3
  • 4
自定义Realm
public class MyRealm implements Realm {
    @Override
    
    public String getName() {
        return "myrealm";
    }
    @Override
    public boolean supports(AuthenticationToken token) {
        //仅支持UsernamePasswordToken类型的Token
        return token instanceof UsernamePasswordToken; 
    }
    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String)token.getPrincipal();  //得到用户名
        String password = new String((char[])token.getCredentials()); //得到密码
        if(!"zhang".equals(username)) {
            throw new UnknownAccountException(); //如果用户名错误
        }
        if(!"123".equals(password)) {
            throw new IncorrectCredentialsException(); //如果密码错误
        }
        //如果身份认证验证成功,返回一个AuthenticationInfo实现;
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

四、Shiro InI配置(通常直接使用ShiroConfig)

1、INI配置

ini 配置文件类似于 Java 中的 properties(key=value),不过提供了将 key/value 分类的特性,key 是每个部分不重复即可,而不是整个配置文件。如下是 INI 配置分类:

[main]
#提供了对根对象securityManager及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
…………
securityManager.realms=$jdbcRealm
[users]
#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
[roles]
#提供了角色及权限之间关系的配置,角色=权限1,权限2
role1=permission1,permission2
[urls]
#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms["permission1"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2、InI配置中的四大类
  • [main]部分

提供了对根对象 securityManager 及其依赖对象(Realm、authenticator、authcStrategy)的配置。

#声明一个realm  
MyRealm1=com.shiro.mutilrealm.MyRealm1
MyRealm2=com.shiro.mutilrealm.MyRealm2
 
#配置验证器
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
 
# AllSuccessfulStrategy 表示 MyRealm1和MyRealm2 认证都通过才算通过
#配置策略
#authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
authcStrategy = com.shiro.authenticationstrategy.MyAuthenticationStrategy
 
 
#将验证器和策略关联起来
authenticator.authenticationStrategy = $authcStrategy
 
 
#配置验证器所使用的Realm
authenticator.realms=$MyRealm2,$MyRealm1
 
#把Authenticator设置给securityManager
securityManager.authenticator = $authenticator
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • [users] 部分

配置用户名 / 密码及其角色,格式:“用户名 = 密码,角色 1,角色 2”,角色部分可省略。

[users]
zhang=123,role1,role2
wang=123
  • 1
  • 2
  • 3
  • [roles] 部分

配置角色及权限之间的关系,格式:“角色 = 权限 1,权限 2”;

[roles]
role1=user:create,user:update
role2=*
  • 1
  • 2
  • 3

这里涉及到密码,就牵扯到加密的问题,我们可以MD5,Sha1,Sha256等算法进行加密

[main]
#告诉shiro我们用哪个加密算法
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
iniRealm.credentialsMatcher = $sha256Matcher

[users] 
#用户名=密码,角色
admin=355b1bbfc96725cdce8f4a2708fda310a80e6d13315aec4e5eed2a75fe8032ce,role1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

获取密码的 hex 加密字符串
String ss = new Sha256Hash(“cc”).toHex();

  • [urls] 部分

配置 url 及相应的拦截器之间的关系,格式:“url = 拦截器 [参数],拦截器 [参数]

/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

anon: 表示匿名访问,即不需要登录就可以访问,例如/login这个路径可以匿名访问
authc: 表示需要登录才能访问
roles[admin]: 表示有admin这个角色的用户才能访问
perms[“user:create”]: 表示有这个权限的才能访问

:匹配一个字符
*:匹配零个或多个字符
**:匹配零个或多个路径

五、Shiro编码加密

1、散列算法
  • MD5加密

    String str = “lyp”;
    String salt = “123”;
    String md5 = new Md5Hash(str, salt).toString();//还可以转换为 toBase64()/toHex()

如上代码通过盐 “123” MD5散列 “lyp”,另外散列时还可以指定散列次数。
如2次表示:
md5(md5(str)):“new Md5Hash(str, salt, 2).toString()

  • SHA256算法

    String str = “lyp”;
    String salt = “123”;
    String sha1 = new Sha256Hash(str, salt).toString();

  • SHA1算法

    String str = “lyp”;
    String salt = “123”;
    //内部使用MessageDigest
    String simpleHash = new SimpleHash(“SHA-1”, str, salt).toString();

  • SHA515算法

为了方便使用,Shiro 提供了 HashService,默认提供了 DefaultHashService 实现。

DefaultHashService hashService = new DefaultHashService(); //默认算法SHA-512
hashService.setHashAlgorithmName("SHA-512");
hashService.setPrivateSalt(new SimpleByteSource("123")); //私盐,默认无
hashService.setGeneratePublicSalt(true);//是否生成公盐,默认false
hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());//用于生成公盐。默认就这个
hashService.setHashIterations(1); //生成Hash值的迭代次数
HashRequest request = new HashRequest.Builder()
            .setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello"))
            .setSalt(ByteSource.Util.bytes("123")).setIterations(2).build();
String hex = hashService.computeHash(request).toHex();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 首先创建一个 DefaultHashService,默认使用 SHA-512 算法;
  • 以通过 hashAlgorithmName 属性修改算法;
  • 可以通过 privateSalt 设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐;
  • 可以通过 generatePublicSalt 属性在用户没有传入公盐的情况下是否生成公盐;
  • 可以设置 randomNumberGenerator 用于生成公盐;
  • 可以设置 hashIterations 属性来修改默认加密迭代次数;
  • 需要构建一个 HashRequest,传入算法、数据、公盐、迭代次数。

SecureRandomNumberGenerator 用于生成一个随机数:

SecureRandomNumberGenerator randomNumberGenerator =
     new SecureRandomNumberGenerator();
randomNumberGenerator.setSeed("123".getBytes());
String hex = randomNumberGenerator.nextBytes().toHex();
  • 1
  • 2
  • 3
  • 4
2、加密/解密
方式一

DefaultPasswordService 配合 PasswordMatcher 实现简单的密码加密与验证服务

1、定义Realm

public class MyRealm extends AuthorizingRealm {
    private PasswordService passwordService;
    public void setPasswordService(PasswordService passwordService) {
        this.passwordService = passwordService;
    }
     //省略doGetAuthorizationInfo,具体看代码 
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return new SimpleAuthenticationInfo( "wu",passwordService.encryptPassword("123"),getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

为了方便,直接注入一个 passwordService 来加密密码,实际使用时需要在 Service 层使用 passwordService 加密密码并存到数据库。

2、ini 配置(shiro-passwordservice.ini)

[main]
passwordService=org.apache.shiro.authc.credential.DefaultPasswordService
hashService=org.apache.shiro.crypto.hash.DefaultHashService
passwordService.hashService=$hashService
hashFormat=org.apache.shiro.crypto.hash.format.Shiro1CryptFormat
passwordService.hashFormat=$hashFormat
hashFormatFactory=org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory
passwordService.hashFormatFactory=$hashFormatFactory
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService=$passwordService
myRealm=com.github.zhangkaitao.shiro.chapter5.hash.realm.MyRealm
myRealm.passwordService=$passwordService
myRealm.credentialsMatcher=$passwordMatcher
securityManager.realms=$myRealm
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • passwordService 使用 DefaultPasswordService,如果有必要也可以自定义;
  • hashService 定义散列密码使用的 HashService,默认使用 DefaultHashService(默认 SHA-256 算法);
  • hashFormat 用于对散列出的值进行格式化,默认使用 Shiro1CryptFormat,另外提供了 Base64Format 和 HexFormat,对于>+ 有 salt 的密码请自定义实现 ParsableHashFormat 然后把 salt 格式化到散列值中;
  • hashFormatFactory 用于根据散列值得到散列的密码和 salt;因为如果使用如 SHA 算法,那么会生成一个 salt,此 salt 需要>+ 保存到散列后的值中以便之后与传入的密码比较时使用;默认使用 DefaultHashFormatFactory;
  • passwordMatcher 使用 PasswordMatcher,其是一个 CredentialsMatcher 实现;
  • 将 credentialsMatcher 赋值给 myRealm,myRealm 间接继承了 AuthenticatingRealm,其在调用 getAuthenticationInfo 方法获取到 AuthenticationInfo 信息后,会使用 credentialsMatcher 来验证凭据是否匹配,如果不匹配将抛出 IncorrectCredentialsException 异常。

如上方式的缺点是:salt 保存在散列值中;没有实现如密码重试次数限制。

方式二

HashedCredentialsMatcher 实现密码验证服务

  • Shiro 提供了 CredentialsMatcher 的散列实现 HashedCredentialsMatcher,和之前的 PasswordMatcher 不同的是,它只用于密码验证,且可以提供自己的盐,而不是随机生成盐,且生成密码散列值的算法需要自己写,因为能提供自己的盐。

1、生成密码散列值

此处我们使用 MD5 算法,“密码 + 盐(用户名 + 随机数)” 的方式生成散列值:

String algorithmName = "md5";
String username = "lyp";
String password = "123";
String salt1 = username;
String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
int hashIterations = 2;
SimpleHash hash = new SimpleHash(algorithmName, password, salt1 + salt2, hashIterations);
String encodedPassword = hash.toHex();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果要写用户模块,需要在新增用户 / 重置密码时使用如上算法保存密码,将生成的密码及 salt2 存入数据库(因为我们的散列算法是:md5(md5(密码 +username+salt2)))。

2、生成 Realm

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String username = "lyp"; //用户名及salt1
    String password = "202cb962ac59075b964b07152d234b70"; //加密后的密码
    String salt2 = "202cb962ac59075b964b07152d234b70";
SimpleAuthenticationInfo ai = new SimpleAuthenticationInfo(username, password, getName());
    ai.setCredentialsSalt(ByteSource.Util.bytes(username+salt2)); //盐是用户名+随机数
        return ai;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3、ini 配置

[main]
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
myRealm=com.github.zhangkaitao.shiro.chapter5.hash.realm.MyRealm2
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 通过 credentialsMatcher.hashAlgorithmName=md5 指定散列算法为 md5,需要和生成密码时的一样;
  • credentialsMatcher.hashIterations=2,散列迭代次数,需要和生成密码时的意义;
  • credentialsMatcher.storedCredentialsHexEncoded=true 表示是否存储散列后的密码为 16 进制,需要和生成密码时的一样,默认是 base64;

此处最需要注意的就是 HashedCredentialsMatcher 的算法需要和生成密码时的算法一样。另外 HashedCredentialsMatcher 会自动根据 AuthenticationInfo 的类型是否是 SaltedAuthenticationInfo 来获取 credentialsSalt 盐。

在这里插入图片描述

3、加密登录处理 HashedCredentialsMatcher方式

需要确保存入数据库的密码与如下的加密方式要一直,如md5加密,盐值为’lyp’ ,散列次数2

  • 自定义Realm

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String username = (String) token.getPrincipal();
    // 往数据库中查询用户
    User user = userMapper.selectOne(new QueryWrapper().lambda().eq(User::getUsername, username));
    if (user == null) {
    // 账号不存在
    throw new UnknownAccountException();
    }
    return new SimpleAuthenticationInfo(
    user, //用户
    user.getPassword(), //密码
    ByteSource.Util.bytes(“lyp”), // 加密传入盐值
    getName());//Realm name
    }

  • ShiroConifg

    /**

    • shiro配置类

    */
    @Configuration
    public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 登录配置
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setSuccessUrl("/");
        shiroFilter.setUnauthorizedUrl("/error/403");
        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("mlfc", new MyLoginFilter());
        shiroFilter.setFilters(filtersMap);
        // 拦截配置
        Map<String, String> filterChainDefinitions = new LinkedHashMap<>();
        filterChainDefinitions.put("/assets/**", "anon");
        filterChainDefinitions.put("/login", "anon");
        filterChainDefinitions.put("/reg", "anon");
        filterChainDefinitions.put("/logout", "logout");
        filterChainDefinitions.put("/**", "mlfc,user");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitions);
        return shiroFilter;
    }
    
    @Bean(name = "userRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public MyRealm userRealm() {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(credentialsMatcher()); // 自定义加密规则
        return myRealm;
    }
    
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm());//将Realm注入到SecurityManager中。
        return securityManager;
    }
    
        //因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
    @Bean(name = "credentialsMatcher)"
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    }

  • 登录接口

    shiro 登录核心
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //封装用户的登录数据
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //执行登录方法,如果没有异常就说明OK了
    subject.login(token);

    /**
     * 登录
     */
    @PostMapping("/login")
    @ResponseBody
    public R login(HttpServletRequest request, String username, String password, String code, boolean rememberMe) {
        if (StringUtil.isBlank(username, password)) {
            return R.failed("账号或密码不能为空");
        }
        String sessionCode = (String) request.getSession().getAttribute("captcha");
        if (code == null || !sessionCode.equals(code.trim().toLowerCase())) {
            return R.failed("验证码不正确");
        }
        try {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
            SecurityUtils.getSubject().login(token);
            return R.succeed("登录成功");
        } catch (UnknownAccountException e) {
            return R.failed("用户不存在");
        } catch (IncorrectCredentialsException e) {
            return R.failed("密码错误");
        } catch (ExcessiveAttemptsException eae) {
            return R.failed("操作频繁,请稍后再试");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • shiro默认登录过期时间是30分钟

    //永不过期,在登陆最开始加上
    SecurityUtils.getSubject().getSession().setTimeout(-1000L);
    //其他时间 单位毫秒
    SecurityUtils.getSubject().getSession().setTimeout(1800000);

六、Shiro 的默认Filter

Filter名称

说明

anon

无参,匿名访问,无需认证就可访问

无参,匿名访问,无需认证就可访问

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

无参,表示需要认证才能访问

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

无参,表示需要httpBasic认证才能访问

logout

org.apache.shiro.web.filter.authc.LogoutFilter

退出拦截器,即退出后重定向的地址

noSessionCreation

org.apache.shiro.web.filter.session.NoSessionCreationFilter

不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常;

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

权限授权拦截器,验证用户是否拥有所有权限

port

org.apache.shiro.web.filter.authz.PortFilter

端口拦截器,端口号不是指定端口号,则跳转过去

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

rest风格拦截器,根据请求方式来识别

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

拥有某个角色权限才能访问

ssl

org.apache.shiro.web.filter.authz.SslFilter

无参,SSL拦截器,只有请求协议是https才能通过

user

org.apache.shiro.web.filter.authc.UserFilter

无参,用户已经身份验证,或记住我登录可以访问

示例:
  1. /admin/**=anon :无参,表示可匿名访问
  2. /admin/user/**=authc :无参,表示需要认证才能访问
  3. /admin/user/**=authcBasic :无参,表示需要httpBasic认证才能访问
  4. /admin/user/**=ssl :无参,表示需要安全的URL请求,协议为https
  5. /home=user :表示用户不一定需要通过认证,只要曾被 Shiro 记住过登录状态就可以正常发起 /home 请求
  6. /edit=authc,perms[admin:edit]:表示用户必需已通过认证,并拥有 admin:edit 权限才可以正常发起 /edit 请求
  7. /admin=authc,roles[admin] :表示用户必需已通过认证,并拥有 admin 角色才可以正常发起 /admin 请求
  8. /admin/user/**=port[8081] :当请求的URL端口不是8081时,跳转到schemal://serverName:8081queryString
  9. /admin/user/**=rest[user] :根据请求方式来识别,相当于 /admins/user/**=perms[user:get]或perms[user:post] 等等
  10. /admin**=roles["admin,guest"] :允许多个参数(逗号分隔),此时要全部通过才算通过,相当于hasAllRoles()
  11. /admin**=perms["user:add:*,user:del:*"]:允许多个参数(逗号分隔),此时要全部通过才算通过,相当于isPermitedAll()

七、shiro注解权限控制-5个权限注解

一般使用@RequiresPermissions即可

Shiro共有5个注解
  • RequiresAuthentication:

    使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。

  • RequiresGuest:

    使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。

  • RequiresPermissions:

    当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。

  • RequiresRoles:

    当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。

  • RequiresUser

    当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

八 . Shiro - jsp标签

在这里插入图片描述

  • 在页面上,如果要实现对某些文本、按钮等的控制,例如需要有什么角色或者权限才可以看见这个按钮,利用shiro自带的shiro标签能很容易就实现
1、引入标签
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro">
  • 1
2、 shiro 标签
  1. shiro:authenticated (表示已认证通过,但不包括remember me登录的)
  2. shiro:guest (表示是游客身份,没有登录)
  3. shiro:hasAnyRoles(表示拥有这些角色中其中一个)
  4. shiro:hasPermission(表示拥有某一权限)
  5. shiro:hashRole (表示拥有某一角色)
  6. shiro:lacksPermission (表示不拥有某一角色)
  7. shiro:lacksRole (表示不拥有某一角色)
  8. shiro:notAuthenticated (表示没有通过验证)
  9. shiro:principal (表示用户的身份)
  10. shiro:user (表示已登录)

1.shiro:authenticated (表示已认证通过,但不包括remember me登录的)

<shiro:authenticated>
    <label>用户身份验证已通过 </label>
</shiro:authenticated>
  • 1
  • 2
  • 3

说明:只有已通过用户认证,但不是通过记住我(remember me)浏览才会看到标签内的内容

2.shiro:guest (表示是游客身份,没有登录)

<shiro:guest>
    <label>您当前是游客,</label><a href="/login.jsp" >请登录</a>
</shiro:guest>
  • 1
  • 2
  • 3

说明:只有是没有登录过,以游客的身份浏览才会看到标签内的内容

3.shiro:hasAnyRoles(表示拥有这些角色中其中一个)

<shiro:hasAnyRoles name="admin,user">
    <label>这是拥有admin或者是user角色的用户</label>
</shiro:hasAnyRoles>
  • 1
  • 2
  • 3

说明:只有成功登录后,且具有admin或者user角色的用户才会看到标签内的内容;name属性中可以填写多个角色名称,以逗号(,)分隔

4.shiro:hasPermission(表示拥有某一权限)

<shiro:hasPermission name="admin:add">
    <label>这个用户拥有admin:add的权限</label>
</shiro:hasPermission>
  • 1
  • 2
  • 3

说明:只有成功登录后,且具有admin:add权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称

5.shiro:hashRole (表示拥有某一角色)

<shiro:hasRole name="admin">
    <label>这个用户拥有的角色是admin</label>
</shiro:hasRole>
  • 1
  • 2
  • 3

说明:只有成功登录后,且具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称

6.shiro:lacksPermission (表示不拥有某一权限)

<shiro:lacksPermission name="admin:delete">
    <label>这个用户不拥有admin:delete的权限</label>
</shiro:lacksPermission>
  • 1
  • 2
  • 3

说明:只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称

7.shiro:lacksRole (表示不拥有某一角色)

<shiro:lacksRole name="admin">
    <label>这个用户不拥有admin的角色</label>
</shiro:lacksRole>
  • 1
  • 2
  • 3

说明:只有成功登录后,且不具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称

8.shiro:notAuthenticated (表示没有通过验证)

<shiro:notAuthenticated>
    <label>用户身份验证没有通过(包括通过记住我(remember me)登录的) </label>
</shiro:notAuthenticated>
  • 1
  • 2
  • 3

说明:只有没有通过验证的才可以看到标签内的内容,包括通过记住我(remember me)登录的

9.shiro:principal (表示用户的身份)

  • 取值取的是你登录的时候,在Realm 实现类中的new SimpleAuthenticationInfo(第一个参数,…) 放的第一个参数:


    return new SimpleAuthenticationInfo(user,user.getPswd(), getName());

  • 1)如果第一个放的是username或者是一个值 ,那么就可以直接用。

    <shiro: principal/>

  • 2)如果第一个参数放的是对象,比如放User 对象。那么如果要取其中某一个值,可以通过property属性来指定。

    <shiro:principal property=“username”/>

10.shiro:user (表示已登录)

<shiro:user>
    <label>欢迎[<shiro:principal/>],</label><a href="/logout.jsp">退出</a>
</shiro:user>
  • 1
  • 2
  • 3

说明:只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容;一般和标签shiro:principal一起用,来做显示用户的名称


注意:
shiro的jsp标签可以嵌套使用,可以根据业务的具体场景进行使用。例如一个按钮需要排除不是admin或user角色的用户才可以显示,可以像如下这样实现:

<shiro:lacksRole name="admin">
    <shiro:lacksRole name="user"> 
        <label>这个用户不拥有admin或user的角色</label>
    </shiro:lacksRole>
</shiro:lacksRole>
  • 1
  • 2
  • 3
  • 4
  • 5

九、示例Demo1(通过自定义token处理)

1、用户的角色与权限

用户

角色

权限

svip

svip

[查看、更新、删除、新增]

vip

vip

[查看、更新、新增]

user

p

[查看]

  • 用户表
    在这里插入图片描述

  • 角色表
    在这里插入图片描述

  • 权限表
    在这里插入图片描述

  • 用户角色关系表
    在这里插入图片描述

  • 角色权限关系表

在这里插入图片描述

2、编写ShiroConfig
/**
 *  ShiroConfig配置
 */
@Configuration
public class ShiroConfig {

    //SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
    @Bean("securityManager")
    public SecurityManager securityManager(AuthRealm authRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(authRealm); //将Realm注入到SecurityManager中。
        return securityManager;
    }
    /**
     *         anno: 无需认证就可以访问
     *         authc: 必须认证才能访问
     *         user: 必须拥有 记住我 功能才能用
     *         perms: 拥有对某个资源的权限才能访问
     *         role: 拥有某个角色权限才能访问
     */

    /**
     * shiro如果使用ShiroConfig中shiroFiltet的map进行权限或角色拦截,会出现只走登陆认证,不走授权认证的情况。这是个巨坑!
     //主要是这部分: 不要用这种方法,最好用注解的方法
     filterMap.put("/add", "roles[admin]");
     filterMap.put("/list", "roles[admin,user]");
     filterMap.put("/delete", "perms[admin:delete]");
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        //auth过滤
        Map<String, Filter> filters = new HashMap<>();
        filters.put("auth", new AuthFilter());
        shiroFilter.setFilters(filters);
        Map<String, String> filterMap = new LinkedHashMap<>();
        //authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。
        // anno匿名访问  auth验证
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/doc.html", "anon");
        // 除了以上路径,其他都需要权限验证
        filterMap.put("/**", "auth");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     *  
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    
    //负责管理shiro的生命周期,项目中不加lifecyclebeanpostprocessor也没问题
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
        
        
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
3、自定义Realm
@Component
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private ShiroService shiroService;

    private String salt = "lyp";

    @Override
    /**
     * 授权 获取用户的角色和权限
     *@param  [principals]
     *@return org.apache.shiro.authz.AuthorizationInfo
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1. 从 PrincipalCollection 中来获取登录用户的信息
        User user = (User) principals.getPrimaryPrincipal();
        //Integer userId = user.getUserId();
        //2.添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
            //2.1添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            for (Permission permission : role.getPermissions()) {
                //2.1.1添加权限
                simpleAuthorizationInfo.addStringPermission(permission.getPermission());
            }
        }
        return simpleAuthorizationInfo;
    }

    @Override
    /**
     * 认证 判断token的有效性
     *@param  [token]
     *@return org.apache.shiro.authc.AuthenticationInfo
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取token,既前端传入的token
        String accessToken = (String) token.getPrincipal();
        //1. 根据accessToken,查询用户信息
        SysToken tokenEntity = shiroService.findByToken(accessToken);
        //2. token失效
        if (tokenEntity == null || tokenEntity.getExpireTime().isBefore(LocalDateTime.now())) {
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }
        //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
        User user = shiroService.findByUserId(tokenEntity.getUserId());
        //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
        if (user == null) {
            throw new UnknownAccountException("用户不存在!");
        }

        //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                user, //用户
                accessToken,
                this.getName());  //Realm name
        return info;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
4、测试接口
@Api(tags = "测试")
@RestController
public class TestController {

    @RequiresPermissions({"save"})
    @PostMapping("/save")
    public Map<String, Object> save(@RequestHeader("token")String token) {
        System.out.println("save");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有save的权力");
        return map;
    }

    @RequiresPermissions({"delete"})
    @DeleteMapping("/delete")
    public Map<String, Object> delete(@RequestHeader("token")String token) {
        System.out.println("delete");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有delete的权力");
        return map;
    }

    @RequiresPermissions({"update"})
    @PutMapping("update")
    public Map<String, Object> update(@RequestHeader("token")String token) {
        System.out.println("update");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有update的权力");
        return map;
    }

    @RequiresPermissions({"select"})
    @GetMapping("select")
    public Map<String, Object> select(@RequestHeader("token")String token) {
        System.out.println("select");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有select的权力");
        return map;
    }

    @RequiresRoles({"vip"})
    @GetMapping("/vip")
    public Map<String, Object> vip(@RequestHeader("token")String token) {
        System.out.println("vip");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有VIP角色");
        return map;
    }
    @RequiresRoles({"svip"})
    @GetMapping("/svip")
    public Map<String, Object> svip(@RequestHeader("token")String token) {
        System.out.println("svip");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有SVIP角色");
        return map;
    }
    @RequiresRoles({"p"})
    @GetMapping("/p")
    public Map<String, Object> p(@RequestHeader("token")String token) {
        System.out.println("p");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有P角色");
        return map;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
5、登录处理
@Api(tags = "Shiro权限管理")
@RestController
public class ShiroController {

    private final ShiroService shiroService;

    public ShiroController(ShiroService shiroService) {
        this.shiroService = shiroService;
    }

    /**
     * 登录
     */
    @ApiOperation(value = "登陆", notes = "参数:用户名 密码")
    @PostMapping("/sys/login")
    public Map<String, Object> login(@RequestBody @Validated LoginDTO loginDTO, BindingResult bindingResult) {
        Map<String, Object> result = new HashMap<>();
        if (bindingResult.hasErrors()) {
            result.put("status", 400);
            result.put("msg", bindingResult.getFieldError().getDefaultMessage());
            return result;
        }

        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();


        //用户信息
        User user = shiroService.findByUsername(username);

        //账号不存在、密码错误
        if (user == null || !user.getPassword().equals(password)) {
            result.put("status", 400);
            result.put("msg", "账号或密码有误");
        } else {
            //生成token,并保存到数据库
            result = shiroService.createToken(user.getUserId());
            result.put("status", 200);
            result.put("msg", "登陆成功");
        }
        return result;
    }

    /**
     * 退出
     */
    @ApiOperation(value = "登出", notes = "参数:token")
    @PostMapping("/sys/logout")
    public Map<String, Object> logout(@RequestHeader("token")String token) {
        Map<String, Object> result = new HashMap<>();
        shiroService.logout(token);
        result.put("status", 200);
        result.put("msg", "您已安全退出系统");
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

通过自定义过滤器拦截,拒绝访问的请求通过executeLogin方法,校验是否能通过

**
 * Shiro自定义auth过滤器
 *
 */
@Component
public class AuthFilter extends AuthenticatingFilter {


    // 定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 生成自定义token
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = TokenUtil.getRequestToken((HttpServletRequest) request);

        return new AuthToken(token);
    }

    /**
     * 步骤1.所有请求全部拒绝访问
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return false;
    }

    /**
     * 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回
        String token = TokenUtil.getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
            httpResponse.setCharacterEncoding("UTF-8");
            Map<String, Object> result = new HashMap<>();
            result.put("status", 403);
            result.put("msg", "请先登录");
            String json = MAPPER.writeValueAsString(result);
            httpResponse.getWriter().print(json);
            return false;
        }
        return executeLogin(request, response);
    }

    /**
     * token失效时候调用
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
        httpResponse.setCharacterEncoding("UTF-8");
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            Map<String, Object> result = new HashMap<>();
            result.put("status", 403);
            result.put("msg", "登录凭证已失效,请重新登录");
            String json = MAPPER.writeValueAsString(result);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
        }
        return false;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

查看源码,exeuteLogin内部使用的就是shiro的登录方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHLIYRXi-1640930818998)(C:UsersASUSAppDataRoamingTypora	ypora-user-imagesimage-20211229163935965.png)]

自定义token类要继承UsernamePasswordToken

/**
 * Shiro自定义token类
 *
 */
public class AuthToken extends UsernamePasswordToken {

    private String token;

    public AuthToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这里不继承 AuthenticationToken,因为getAuthenticationTokenClass()实际上获取到的是UsernamePasswordToken.class

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awMYRMVb-1640930818999)(C:UsersASUSAppDataRoamingTypora	ypora-user-imagesimage-20211229164805817.png)]

十、示例Demo2 (shiro自动生成token,并实现记住我、md5加密和缓存)

1、用户的角色与权限

用户

角色

权限

admin

admin

[查看文件,拷贝文件、移动文件、重命名文件、下载文件、删除文件、上传文件、目录管理、创建目录、文件打分、评论文件、查看得分]

lyp

user

[查看文件,拷贝文件、下载文件、上传文件、目录管理、评论文件、查看得分]

2、编写ShiroConfig
/**
 * shiro配置类
 *
 */
@Configuration
public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 登录配置
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setSuccessUrl("/");
        shiroFilter.setUnauthorizedUrl("/error/403");
        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("mlfc", new MyLoginFilter());
        shiroFilter.setFilters(filtersMap);
        // 拦截配置
        Map<String, String> filterChainDefinitions = new LinkedHashMap<>();
        filterChainDefinitions.put("/assets/**", "anon");
        filterChainDefinitions.put("/login", "anon");
        filterChainDefinitions.put("/reg", "anon");
        filterChainDefinitions.put("/logout", "logout");
        filterChainDefinitions.put("/**", "mlfc,user");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitions);
        return shiroFilter;
    }

    
    @Bean(name = "userRealm")
    @DependsOn("lifecycleBeanPostProcessor")//依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到IOC容器中。
    public MyRealm userRealm() {
        MyRealm myRealm = new MyRealm();//自定义的Realm
        myRealm.setCredentialsMatcher(credentialsMatcher()); // 将加密规则注入Realm
        return myRealm;
    }

    //SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm());//将Realm注入到SecurityManager中。
        securityManager.setCacheManager(cacheManager());//注入缓存对象。
        securityManager.setRememberMeManager(rememberMeManager());//注入rememberMeManager()
        return securityManager;
    }

    @Bean(name = "cacheManager")
    public EhCacheManager cacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:shiro/ehcache-shiro.xml");
        return cacheManager;
    }

  
     // 自定义加密规则
    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        credentialsMatcher.setHashIterations(3);
        return credentialsMatcher;
    }

   //负责管理shiro的生命周期,项目中不加lifecyclebeanpostprocessor也没问题
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        LifecycleBeanPostProcessor lifecycleBeanPostProcessor = new LifecycleBeanPostProcessor();
        return lifecycleBeanPostProcessor;
    }

    /**
     * shiro里实现的Advisor类,用来拦截注解的方法 .
     *
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }

    /**
     * 实现spring的自动代理
     * 这个类将扫描上下文,寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成),将这些Advisor应用到所有符合切入点的Bean中,
     * 即为匹配的目标Bean自动创建代理
    */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 配置ShiroDialect:用于thymeleaf和shiro标签配合使用
     */
    @Bean("shiroDialect")
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

    //cookie对象
    @Bean
    public SimpleCookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    //cookie管理对象  处理记住我功能
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return cookieRememberMeManager;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
3、自定义Realm
/**
 * 自定义认证授权处理逻辑
 *
 */
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private RoleAuthMapper roleAuthMapper;

    /**
    *  授权 获取用户的角色和权限
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        List<String> roleList = userRoleMapper.selectRoleCodesById(user.getId());
        List<String> authList = roleAuthMapper.selectAuthCodesByRoleCodes(roleList);
        Set<String> roleSet = new HashSet<>(roleList);
        Set<String> authSet = new HashSet<>(authList);
        authorizationInfo.setRoles(roleSet);
        authorizationInfo.setStringPermissions(authSet);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        // 往数据库中查询用户
        User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getUsername, username));
        if (user == null) {
            // 账号不存在
            throw new UnknownAccountException();
        }
        // 密码验证shiro处理
        return new SimpleAuthenticationInfo(
            user,  //用户
            user.getPassword(), //密码
            ByteSource.Util.bytes("lyp"),  // 加密传入盐值
            getName());//Realm name
        );

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
4、测试接口
@RestController
public class ScoreController extends BaseController{

    @Autowired
    ScoreServiceImpl scoreService;

    @Autowired
    UserService userService;

    /**
     * 文件打分
     * @param score
     * @return
     */
    @RequiresPermissions("file:score")
    @PostMapping("/addScore")
    public R addScore(Score score){
        System.out.println("获取结果:"+score);
        score.setUserId(getLoginUserId());
        if(scoreService.addScore(score)){

            return R.succeed("打分成功");
        }

        return R.failed("打分失败");
    }

    /**
     * 获取文件得分
     */
    @RequiresPermissions("file:check")
    @GetMapping("/getScore/{fileId}")
    public R getScore(@PathVariable Long fileId){
        Score score = scoreService.queryScoreByFId(fileId);
        return R.succeed(score,"查询成功");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
5、登录处理
    /**
     * 登录
     */
    @PostMapping("/login")
    @ResponseBody
    public R login(HttpServletRequest request, String username, String password, String code, boolean rememberMe) {
        if (StringUtil.isBlank(username, password)) {
            return R.failed("账号或密码不能为空");
        }
        String sessionCode = (String) request.getSession().getAttribute("captcha");
        if (code == null || !sessionCode.equals(code.trim().toLowerCase())) {
            return R.failed("验证码不正确");
        }
         //获取当前用户
 		 Subject subject = SecurityUtils.getSubject();
		 //封装用户的登录数据
         UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
            
        try {
            //执行登录方法,如果没有异常就说明OK了
 			subject.login(token);
            return R.succeed("登录成功");
        } catch (UnknownAccountException e) {
            return R.failed("用户不存在");
        } catch (IncorrectCredentialsException e) {
            return R.failed("密码错误");
        } catch (ExcessiveAttemptsException eae) {
            return R.failed("操作频繁,请稍后再试");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
6、前端权限控制

引入标签

<%@ taglib uri=“http://shiro.apache.org/tags” prefix=“shiro”>

<shiro:hasPermission="file:view" id="open"> 查看</a>
  • 1

请添加图片描述

请添加图片描述

请添加图片描述

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/534010
推荐阅读
相关标签
  

闽ICP备14008679号