C'est le 26e jour de ma participation au défi de mise à jour d'août. Pour plus de détails sur l'événement, veuillez consulter : Défi de mise à jour d'août
1. Introduction
-
Apache Shiro est un framework de sécurité (autorisation) pour Java ;
-
Shiro peut facilement développer une application assez bonne, qui peut être utilisée non seulement dans l'environnement JavaSE, mais aussi dans l'environnement JavaEE ;
-
Fonctions principales : Authentification, Autorisation, chiffrement, gestion des sessions, intégration avec le Web, mise en cache, etc. ;
Architecture de Shiro
- Sujet : L'objet avec lequel le code de l'application interagit directement est le Sujet , ce qui signifie que le cœur de l'API externe de Shiro est le Sujet. Le sujet représente "l'utilisateur" actuel , cet utilisateur n'est pas nécessairement une personne spécifique, tout ce qui interagit avec l'application actuelle est un sujet, comme les robots d'indexation Web, les robots, etc. ; toutes les interactions avec le sujet seront déléguées à SecurityManager ; le sujet est en fait a Sur la façade, SecurityManager est le véritable exécuteur ;
- SecurityManager : Security Manager ; c'est-à-dire que toutes les opérations liées à la sécurité interagiront avec SecurityManager ; et il gère tous les sujets ; on peut voir que c'est le noyau de Shiro, qui est responsable de l'interaction avec les autres composants de Shiro , ce qui est équivalent au DispatcherServlet dans le caractère SpringMVC de
- Realm : Shiro obtient des données de sécurité (telles que les utilisateurs, les rôles, les autorisations) de Realm, c'est-à-dire que si le SecurityManager veut vérifier l'identité de l'utilisateur, il doit alors obtenir l'utilisateur correspondant de Realm pour comparaison afin de déterminer si l'utilisateur l'identité est légale ; il doit également obtenir l'identité correspondante de l'utilisateur auprès de Realm. Le rôle/l'autorité pour vérifier si l'utilisateur peut fonctionner ; Realm peut être considéré comme une source de données
2. Développement intégré avec Spring
2.1 Certification Shiro
2.1.1 Fichier de configuration
web.xml
<!-- Shiro 过滤器定义 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为 false,表示生命周期由 SpringApplicationContext;
设置为 true 表示由 ServletContainer 管理
-->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
applicationContext.xml
<!-- 1.安全管理器SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!--2.配置CacheManager
2.1 需要加入ehcache 的jar包,及配置文件
-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 3.自定义Realm
-->
<bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm"/>
<!-- 4.保证实现了Shiro内部lifecycle函数的bean执行
可以来调用IOC容器中 shiro bean 生命周期方法
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 5.开启Shiro注解,必须配置了lifecycleBeanPostProcessor -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 5.Shiro过滤器
id 必须和web.xml文件中配置DelegatingFilterProxy的<filter-name>一致
因为Shiro会在 IOC容器中查询和 <filter-name> 名字对应的 filter bean
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="success.jsp"/>
<property name="unauthorizedUrl" value="unauth.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/login.jsp=anon
/**=authc
</value>
</property>
</bean>
复制代码
MyRealm.java
public class MyRealm extends AuthorizingRealm{
@Resource
private BloggerService bloggerService;
/**
* AuthorizationInfo:角色的权限集合
* 获取授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
/**
* AuthenticationInfo:用户的角色集合
* 登录验证
* token: 令牌,基于用户名密码的名牌
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
// 假设用户名、密码
String username = "xiaojian";
// 1.转换类型,获取Token
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
// 2.对比用户名
if(!token.getUsername().equals(username)){
// 用户名不存在
return null; //// Shiro底层会抛出 UnknownAccountException 异常
}
// 3.根据用户情况,构建AuthenticationInfo对象返回。通常使用实现类:SimpleAuthenticationInfo
// 3.1 principal:认证的实体信息,可以是username,也可以是对应的实体类对象
Object principal = null;
// 3.2 hashedCredentials:密码
Object hashedCredentials = "1234"; //a20b8e682a72eeac0049847855cecb86
// 3.3 realName:当前realm对象的name,调用父类的getName()方法
String realmName = getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = null;
simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,hashedCredentials,realmName);
return simpleAuthenticationInfo;
}
}
复制代码
2.1.2 Le filtre par défaut dans Shiro
classe de filtre | nom du filtre | Exemple |
---|---|---|
anon | Pas de paramètres, accès anonyme | /login.jsp=anon |
authc | Aucun paramètre, une authentification (login) est nécessaire pour accéder | /admin/**=authentique |
utilisateur | Aucun paramètre, indiquant qu'il doit y avoir un utilisateur | |
permanentes | Il peut y avoir plusieurs paramètres, qui doivent être placés entre guillemets, et les paramètres sont séparés par des virgules. Lorsqu'il y a plusieurs paramètres, chaque paramètre doit être passé, ce qui équivaut à la méthode isPermitedAll() | /admin/*=perms[user:add] /admin/**=perms["user:add,user:update"] |
les rôles | Le filtre de rôle, qui détermine si l'utilisateur actuel spécifie un rôle. Les règles sont les mêmes que ci-dessus. Équivalent à la méthode hasAllRoles() | /admin/**=rôles["admin,invité"] |
Se déconnecter | Lors de la déconnexion, une certaine fonction est terminée : toute session existante sera invalide et toute identité sera perdue (dans le programme Web, le cookie RememberMe sera également supprimé) |
2.1.3 Modèle de correspondance d'URL
Utilisez le mode de style Ant, le caractère générique du chemin Ant prend en charge ?, *, **, la correspondance des caractères génériques n'inclut pas le séparateur de répertoire "/"
- ? : correspond à un caractère, tel que : /admin?, correspond à : /admin1 ; ne correspond pas : /admin123, /admin/
- * : correspond à zéro ou plusieurs chaînes ou à un chemin
- ** : correspond à zéro ou plusieurs chemins dans le chemin
2.1.4 Ordre de correspondance des URL
第一次匹配优先的方式。所以一般 /* 的路径访问都放在后面。
2.1.5 Shiro加密
Shiro 认证中密码比对:使用AuthorizingRealm中的 credentialsMatcher 进行的密码比对。
1、md5加密(不可逆的)
(1).如何把一个字符串加密为MD5;
(2).替换当前 Realm的credentialsMatcher 属性,直接使用HashedCredentialsMather对象,并设置加密算法。
applicationContext.xml
<!-- 3.自定义Realm
实现了 Realm 接口的bean
-->
<bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密的次数-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
复制代码
这时候服务器会自动将浏览器传来的密码使用 MD5加密,加密1024次。
2、md5盐值加密
applicationContext.xml不变
(1). doGetAuthenticationInfo方法返回值创建SimpleAuthenticationInfo,使用构造器:
SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);
(2). 使用ByteSource.Util.bytes(username);加密盐值
(3). 盐值需唯一,一般使用随机字符串、user id
(4). 可以使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 计算盐值加密后的值。
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
// 1.转换类型,获取Token
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
//3.根据用户情况,构建AuthenticationInfo对象返回,通常使用实现类 SimpleAuthenticationInfo
// 3.1 principal:认证的实体信息,可以是username,也可以是对应的实体类对象
Object principal = username;
// 3.2 hashedCredentials:密码
Object hashedCredentials = null; //a20b8e682a72eeac0049847855cecb86
// 2.对比用户名、密码
if("user".equals(username)){
hashedCredentials = "3e042e1e3801c502c05e13c3ebb495c9";
} else if("admin".equals(username)){
hashedCredentials = "c34af346c89b8b03438e27a32863c9b5";
}
// 3.3 credentialsSalt:加密盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
// 3.4 realName:当前realm对象的name,调用父类的getName()方法
String realmName = getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);
return simpleAuthenticationInfo;
}
复制代码
MD5Test.java
public class MD5Test {
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object credentials = "1234";
Object salt = "admin";
int hashIterations = 1024;
Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(result);
}
}
复制代码
2.1.6 多Reaml
为什么使用多 Realm ?
根据不同的登录需求,需要做不同的验证。如:手机号登录、邮箱登录。。。
使用 ModularRealmAuthenticator类,属性Collection realms;注入 Realm集合
SecondRealm.java 同MyRealm类,将验证密码改为 SHA1加密后的值。
applicationContext.xml
<bean id="secondRealm" class="com.xiaojian.shiro.realms.SecondRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="SHA1"/>
<!--加密的次数-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
复制代码
认证策略 (AuthenticationStrategy)
AuthenticationStrategy 接口的默认实现:
- FirstSuccessfulStrategy: 还要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy: 只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有 Realm 身份验证成功的认证信息;
- AllSuccessfulStrategy: 所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
- ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略
配置:
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!-- <property name="realms">-->
<!-- <list>-->
<!-- <ref bean="myRealm"/>-->
<!-- <ref bean="secondRealm"/>-->
<!-- </list>-->
<!-- </property>-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
</property>
</bean>
复制代码
使用多 Realm 后,可以把 authenticator 配置给 SecurityManager
通常将所有的 Realm 配置给 安全管理器SecurityManager
之后
<!-- 1.安全管理器SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator" ref="authenticator"/>
<property name="realms">
<list>
<ref bean="myRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
复制代码
2.2 Shiro 授权
多 Realm 实现授权时,有一个通过,都是授权通过。
Shiro过滤器添加角色拦截
/user.jsp = roles[user]
/admin.jsp = roles[admin]
复制代码
MyRealm.java
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("MyRealm 执行授权逻辑");
// 1. 从 principalCollection 获取用户信息 (是用户名还是用户对象,取决于你认证时 principal参数 放入的是啥)
Object principal = principalCollection.getPrimaryPrincipal();
// 2. 利用登录用户的信息来获取当前用户的角色或权限
Set<String> roles = new HashSet<>();
roles.add("user");
if("admin".equals(principal)){
roles.add("admin");
}
// 3. 创建 SimpleAuthorizationInfo,并设置其 roles 属性
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
// 4. 返回 SimpleAuthorizationInfo
return authorizationInfo;
}
复制代码
2.3 Shiro 标签
Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。
标签 | 描述 | 示例 |
---|---|---|
guest | 用户没有身份验证时显示相应信息,即游客信息 | |
user | 用户已经经过认证/记住我登录后显示相应的信息 | |
authenticated | 用户已经身份验证通过,即 Subject.login登录成功,不是记住我登录的 | |
notAuthenticated | L'utilisateur n'est pas authentifié, c'est-à-dire qu'il n'appelle pas Subject.login pour se connecter, y compris en se souvenant que ma connexion automatique n'est pas non plus authentifiée. | |
principal | Afficher les informations d'identité de l'utilisateur, appelées par défaut | |
aRôle | Si le sujet actuel a un rôle, le contenu du corps sera affiché | |
aAnyRoles | Si le sujet actuel a un rôle (ou une relation), le contenu du corps sera affiché. | |
manque de rôle | Si le sujet actuel n'a aucun rôle, le contenu du corps sera affiché | |
aPermission | Si le sujet actuel a la permission, le contenu du corps sera affiché | |
n'a pas la permission | Si le sujet actuel n'a pas la permission, le contenu du corps sera affiché |