关于shiro:
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
本教程只介绍基本的 Shiro 使用,不会过多分析源码等,重在使用。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
基本功能:
认证(Authentication):验证用户是否为合法用户
授权(Authorization):验证某个用户是否有权限访问某个特定的资源
session管理(Session Management):会话管理 管理特定用户的会话,在普通java项目中模拟httpsession的效果
加解密(Cryptography):加解密 使用加密算法确保数据安全,同时仍然易于使用
名词解释:************************
Realm : 域,提供数据的组件
- getAuthencationInfo 入参 Token 出参 Info
- Info 有 Principal + Credentials
AuthenticationToken:认证令牌,认证入参
AuthenticationInfo: 认证信息,用户信息
AuthorizationInfo: 授权信息
Principal: 经过认证的用户
Credentials: 凭证=密码
Shiro完整的异常体系参考:
- RuntimeException
-
- ShiroException
-
-
- AuthenticationException
-
-
-
-
- AccountException
-
-
-
-
-
-
- DisabledAccountException
- UnknownAccountException
-
-
-
-
-
-
- CredentialsException
-
-
-
-
- AuthorizationExeption
-
-
-
-
- UnauthenticatedException
-
-
===========================================
认证(Authentication)
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。
在 shiro 中,用户需要提供 principals
(身份)和 credentials
(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals
,但只有一个 Primary principals
,一般是用户名 / 密码 / 手机号。
credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
最常见的 principals
和 credentials
组合就是用户名 / 密码了。接下来先进行一个基本的身份认证。
而Subject
及 Realm
,分别是主体及验证主体的数据源。
环境准备
1.首先准备环境依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0</version>
</dependency>
添加 junit
、common-logging
及 shiro-core
依赖即可。
2、首先准备一些用户身份 / 凭据(shiro.ini)
如上图roles即是角色而headmaster角色有两个权限,
users即是用户laoliu密码为wohenshuai,有两个角色为headmaster和teacher
3.写代码
Subject也称为门面类,以此类登录(验证用户是否存在且合法),
此外可用subject.isAuthenticated();来验证用户是否是认证状态。
subject.hasRole("xxx角色");判断是否有某个角色。
subject.isPermitted("xxx权限");是否有权限。
关于Spring-boot集成shiro
shiro在spring中的运行原理:
请求先要经过shiro本身的shiroFilter中认证与授权然后传回spring的过滤器实现对于功能此处也解释了shiro的Web支持。
第一步:建项目spring-boot
第二步:导依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.1</version>
</dependency>
第三步:改配置
@Configuration//注意配置类 public class SecurityConfig { @Bean public Realm shiroRealm(){ return new ShiroRealm(); } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition sfcd = new DefaultShiroFilterChainDefinition(); sfcd.addPathDefinition("/","anon"); sfcd.addPathDefinition("/login","anon"); sfcd.addPathDefinition("/login.html","anon"); sfcd.addPathDefinition("/css/**","anon"); sfcd.addPathDefinition("/js/**","anon"); sfcd.addPathDefinition("/images/**","anon"); sfcd.addPathDefinition("/fonts/**","anon"); sfcd.addPathDefinition("/html/**","anon"); //logout 登出 sfcd.addPathDefinition("/logout","logout"); //其他则需要认证 sfcd.addPathDefinition("/**","user"); return sfcd; } @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); /** * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。 * 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。 * 加入这项配置能解决这个bug */ defaultAdvisorAutoProxyCreator.setUsePrefix(true); return defaultAdvisorAutoProxyCreator; } }
上述标红方法可以解决关于重定义(302)无法访问跳转的问题!!!
第四步:写代码
@RestController @Slf4j public class LoginController { @PostMapping("/login") public Result login(String username, String password){ log.info("{}{}",username,password); Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username,password)); return Result.success(); } @GetMapping("/pay") public String pay(){ return "success"; } @GetMapping("/destroyEarth") @RequiresPermissions("睡觉") public String destroyEarth(){ return "success"; } }
注意:在执行登陆认证过后登录信息将会短暂保存,而在保存这个时间段我们称之为session,期间可以访问有权限的特殊资源,在logout后断开。
MybatisPlus实现ShiroRealm
package com.zjh.framework.shiro_boot_demo02.components; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zjh.framework.shiro_boot_demo02.dao.EduPermissionsMapper; import com.zjh.framework.shiro_boot_demo02.dao.EduRoleMapper; import com.zjh.framework.shiro_boot_demo02.dao.EduUserMapper; import com.zjh.framework.shiro_boot_demo02.model.EduRole; import com.zjh.framework.shiro_boot_demo02.model.EduUser; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; @Slf4j public class ShiroRealm extends AuthorizingRealm { @Resource private EduRoleMapper eduRoleMapper; @Resource private EduPermissionsMapper permMapper; @Resource private EduUserMapper userMapper; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { EduUser eduUser = (EduUser)principalCollection.getPrimaryPrincipal(); //查找所有该用户登陆的角色 List<EduRole> roles = eduRoleMapper.SelectByUserId(eduUser.getUserId()); Set<String> collect = roles.stream().map(role -> role.getRoleName()).collect(Collectors.toSet()); List<String> permissions= new ArrayList<>(); if (roles.size()>0){ permissions=permMapper.selectPermInRoleIds(roles); } SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo(); authzInfo.setStringPermissions(new HashSet<>(permissions)); authzInfo.setRoles(collect); return authzInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { Object username = token.getPrincipal(); QueryWrapper<EduUser> qw = new QueryWrapper<>(); qw.eq("user_name",username); EduUser eduUser = userMapper.selectOne(qw); return new SimpleAuthenticationInfo(eduUser,eduUser.getUserPwd(),getClass().getName()); } }
mapper:
<select id="selectPermInRoleIds" resultType="java.lang.String">
select p.permissions_name
from edu_role_perm rp join edu_permissions p on rp.permissions_id=p.permissions_id
where rp.role_id in
<foreach collection="roles" item="r" separator="," open="(" close=")">
#{r.roleId}
</foreach>
</select>
注意:1.导包时注意区分,容易混淆,导致无法运行(注意检查包javax!)
2.yml(propertites)中注意配置mapper-locations
mybatis-plus: mapper-locations: classpath*:mappers/**/*.xml