Shiro——Spring环境下的使用
一、使用
1.搭建基础环境
(1)导入Spring和Shiro的Jar包
正常导入springjar包
导入日志包
log4j-1.2.15.jar
slf4j-api-1.6.1.jar
slf4j-log4j12-1.6.1.jar
导入shiro包
shiro-core-1.2.2.jar
shiro-ehcache-1.2.2.jar
shiro-spring-1.2.2.jar
shiro-web-1.2.2.jar
(2)配置文件
web.xml
读取所有配置文件
springmvc的DispatcherServlet配置
shiroFilter的配置(该配置参考的是:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\web.xml)
见文章末的两个web.xml
spring-shiro.xml
该配置参考:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\applicationContext.xml
此次实验在shiro.xml文件中采用的缓存管理器是ehcache。需要额外导入ehcachejar包和配置文件。
ehcache使用的Hibernate下的。
ehcache-core-2.4.3.jar(hibernate-release-4.2.4.Final\lib\optional\ehcache\)和?ehcache.xml(hibernate-release-4.2.4.Final\project\etc\)
见文章末的两个spring-shiro.xml
注意:(1)缓存的配置(2)自定义Realm
spring.xml正常配置即可。
springmvc.xml正常配置即可。
(3)检测
添加自定义Realm,需要继承自?AuthorizingRealm(即包含认证,也包含授权的Realm)
添加了一个空的自定义的Realm,没有添加认证和授权逻辑。
启动项目,检测能否正常启动,检测配置是否正确。
2.登录
(1)添加登录页面
?login.jsp
(2)对应Handler方法
@RequestMapping("/shiro-login")
publicStringlogin(StringuserName,Stringpassword){
System.out.println("userName:"+userName+",password:"+password);
SubjectcurrentUser=SecurityUtils.getSubject();
if(!currentUser.isAuthenticated()){
UsernamePasswordTokentoken=newUsernamePasswordToken(userName,password);
token.setRememberMe(true);
try{
currentUser.login(token);
}catch(UnknownAccountExceptionuae){
System.out.println("用户名不正确!");
}catch(IncorrectCredentialsExceptionice){
System.out.println("密码不匹配!");
}catch(LockedAccountExceptionlae){
System.out.println("账户被锁定!");
}catch(AuthenticationExceptionae){
System.out.println("认证失败!");
}
}
return"success";
}
这里参考的是官方的demo:shiro-root-1.2.2\samples\quickstart\src\main\java\Quickstart.java
?Quickstart.java
说明一下:
官方demo演示的是一个java项目,而不是一个web项目,不同点是:web项目下,shiro的大管家是由容器去创建的,而不需要我们手动去获取。
(3)检测能否正常运行,若能,则证明登录测试成功。
3.认证
(1)实现自定义Realm的认证方法。
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
System.out.println("开始认证!");
UsernamePasswordTokenupToken=(UsernamePasswordToken)token;
Stringusername=upToken.getUsername();
Objectcredentials="123456";
SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(username,credentials,this.getName());
returninfo;
}
或
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
System.out.println("开始认证!");
UsernamePasswordTokenupToken=(UsernamePasswordToken)token;
Stringusername=upToken.getUsername();
Useruser=newUser();
user.setId(1000);
user.setUserName(username);
user.setPassword("123456");
user.getRoleNames(www.hunanwang.net).add("admin");
returnnewSimpleAuthenticationInfo(user,user.getPassword(),this.getName());
}
说明一下:
其中username是从页面获取到的,而密码是通过查询数据库获取的。注意标红加粗的地方。
实现的参考:org.apache.shiro.realm.jdbc.JdbcRealm
(2)测试,此时密码不为"123456"能否登录。测试认证是否成功。
4.授权
(1)实现自定义Realm的授权方法。
@Override
protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){
System.out.println("开始授权!");
Stringusername=(String)this.getAvailablePrincipal(principalCollection);
System.out.println("userName:"+username);
SetroleNames=newHashSet<>();
roleNames.add("admin");
SimpleAuthorizationInfoinfo=newSimpleAuthorizationInfo(roleNames);
returninfo;
}
或:User对象已经包含角色信息,不需要再次查询数据库。
@Override
protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){
System.out.println("开始授权!");
Useruser=(User)principalCollection.getPrimaryPrincipal();
SetroleNames=user.getRoleNames();
returnnewSimpleAuthorizationInfo(roleNames);
}
实现的参考:org.apache.shiro.realm.jdbc.JdbcRealm
(2)测试,添加对应的页面,然后在Shiro配置文件中配置访问对应的页面需要什么样的角色才能访问。
5.认证和授权的资源数据从数据库中获取
(1)受保护资源和需要角色权限间的关系存在在shiro.xml文件中。
/user.jsp=authc
/admin.jsp=roles[admin]
/=anon
(2)要想改为从数据库中获取,思路就是:filterChainDefinitions属性值能从Java文件中获取。
参看:filterChainDefinitions属性的官方使用
publicvoidsetFilterChainDefinitions(Stringdefinitions){
Iniini=newIni();
ini.load(definitions);
Sectionsection=ini.getSection("urls");
if(CollectionUtils.isEmpty(section)){
section=ini.getSection("www.hunanwang.net");
}
this.setFilterChainDefinitionMap(section);
}
publicvoidsetFilterChainDefinitionMap(MapfilterChainDefinitionMap){
this.filterChainDefinitionMap=filterChainDefinitionMap;
}
实际上放的是一个Map。那么来看看具体的Map存放的是怎么的一些数据格式?在标红的代码处打个断点,可以看到如下内容:
可以看到,Map内容的就是在shiro.xml通过属性?filterChainDefinitions定义的值。
(3)具体操作
/
@authorsolverpeng
@create2016-09-21-18:59
/
publicclassFilterChainDefinitionMapBuilder{
publicMapgetFilterChainDefinitionMap(){
MapfilterChainDefinitionMap=newHashMap<>();
filterChainDefinitionMap.put("/admin.jsp","roles[admin],authc");
filterChainDefinitionMap.put("/user.jsp","authc");
filterChainDefinitionMap.put("/","anon");
returnfilterChainDefinitionMap;
}
}
更改配置:
此时,资源信息可以通过Java方法来处理,而此时,这些数据就可以从数据库中获取。
6.盐值加密
加密指的是对密码的加密,用户注册时,将密码使用一定的加密方式存放到数据库中,用户登录的时候,同样以相同的加密方式进行比对。
在什么地方进行的对比?
注意:不是在自定义认证的时候对比的。在MyRealm中
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
System.out.println("开始认证!");
UsernamePasswordTokenupToken=(UsernamePasswordToken)token;
Stringusername=upToken.getUsername();
Useruser=newUser();
user.setId(1000);
user.setUserName(username);
user.setPassword("42e56621cf3adc9ecc261936188d31d7");
user.getRoleNames().add("admin");
StringhashedCredentials=user.getPassword();
ByteSourcecredentialsSalt=ByteSource.Util.bytes("abcd");
StringrealmName=getName();
SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(user,hashedCredentials,credentialsSalt,
realmName);
returninfo;
}
的这个方法的作用,只是根据传入的Token获取到username,从而到数据库中查询对应的User对象,以及盐值信息,然后返回封装这些信息的?SimpleAuthenticationInfo?的对象。
密码的对比是在这之后对比的,来看返回后,返回到了?org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo这个方法
publicfinalAuthenticationInfogetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
AuthenticationInfoinfo=getCachedAuthenticationInfo(token);
if(info==null){
//otherwisenotcached,performthelookup:
info=doGetAuthenticationInfo(token);
log.debug("LookedupAuthenticationInfo[{}]fromdoGetAuthenticationInfo",info);
if(token!=null&&info!=null){
cacheAuthenticationInfoIfPossible(token,info);
}
}else{
log.debug("Usingcachedauthenticationinfo[{}]toperformcredentialsmatching.",info);
}
if(info!=null){
assertCredentialsMatch(token,info);
}else{
log.debug("NoAuthenticationInfofoundforsubmittedAuthenticationToken[{}].Returningnull.",token);
}
returninfo;
}
其中第一处标红的地方是调用我们自定义Realm的?doGetAuthenticationInfo()方法以及返回值。
第二处标红的地方表示如果可以缓存的话,就把此登录账户进行缓存。
第三处才是真正进行比较的地方,看看方法名和参数,见名知意。token为表单提交过来的用户信息,而info是我们做认证时从数据库查询获取到的。
详细来看:org.apache.shiro.www.visa158.com.realm.AuthenticatingRealm#assertCredentialsMatch
protectedvoidassertCredentialsMatch(AuthenticationTokentoken,AuthenticationInfoinfo)throwsAuthenticationException{
CredentialsMatchercm=getCredentialsMatcher();
if(cm!=null){
if(!cm.doCredentialsMatch(token,info)){
//notsuccessful-throwanexceptiontoindicatethis:
Stringmsg="Submittedcredentialsfortoken["+token+"]didnotmatchtheexpectedcredentials.";
thrownewIncorrectCredentialsException(msg);
}
}else{
thrownewAuthenticationException("ACredentialsMatchermustbeconfiguredinordertoverify"+
"credentialsduringauthentication.Ifyoudonotwishforcredentialstobeexamined,you"+
"canconfigurean"+AllowAllCredentialsMatcher.class.getName()+"instance.");
}
}
两个核心的地方:
第一处标红是:获取凭证的匹配器(密码的匹配器),来看这个接口中封装了一下什么信息。
publicinterfaceCredentialsMatcher{
booleandoCredentialsMatch(AuthenticationTokentoken,AuthenticationInfoinfo);
}
只有一个?doCredentialsMatch(AuthenticationTokentoken,AuthenticationInfoinfo)的方法。
来看它的体系:
看到了?Md5CredentialsMatcher,然后发现它是一个过期的类,HashedCredentialsMatcher作为对它的一个替代,需要setHashAlgorithmName()来指定加密方式。
这里直接对?HashedCredentialsMatcher?给出说明:
(1)加密方式:setHashAlgorithmName(StringhashAlgorithmName)
(2)加密次数:setHashIterations(inthashIterations)
说了这么多,如何由我们自己指定CreaentialsMatcher?
我自定义的MyRealm是?AuthenticatingRealm它的子类,所以想法是,在?AuthenticatingRealm调用?getCredentialsMatcher()之前,就将?CreaentialsMatcherset到Realm中。
来看具体操作:
在MyRealm中定义一个初始化方法:
publicvoidinitCredentialsMatcher(){
HashedCredentialsMatchercredentialsMatcher=newHashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1000);
setCredentialsMatcher(credentialsMatcher);
}
指定了加密方式,然后加密次数,然后设置到了Realm中。
为了保证在在AuthenticatingRealm调用getCredentialsMatcher()之前,就将CreaentialsMatcherset到Realm中,在容器初始化的时候就设置。
applicationContext-shiro.xml的配置:
第二处标红进行的真正的密码匹配:cm.doCredentialsMatch(token,info)
详细来看:org.apache.shiro.authc.credential.HashedCredentialsMatcher#doCredentialsMatch
publicbooleandoCredentialsMatch(AuthenticationTokentoken,AuthenticationInfoinfo){
ObjecttokenHashedCredentials=hashProvidedCredentials(token,info);
ObjectaccountCredentials=getCredentials(info);
returnequals(tokenHashedCredentials,accountCredentials);
}
其中?tokenHashedCredentials?是从表单提交过来的密码经同样的加密方式处理后的凭证;accountCredentials是从数据库中取出来的凭证信息。
关注点:是怎么对表单密码进行加密处理的
详细来看:
org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(org.apache.shiro.authc.AuthenticationToken,org.apache.shiro.authc.AuthenticationInfo)
protectedObjecthashProvidedCredentials(AuthenticationTokentoken,AuthenticationInfoinfo){
Objectsalt=null;
if(infoinstanceofSaltedAuthenticationInfo){
salt=((SaltedAuthenticationInfo)info).getCredentialsSalt();
}else{
//retain1.0backwardscompatibility:
if(isHashSalted()){
salt=getSalt(token);
}
}
returnhashProvidedCredentials(token.getCredentials(),salt,getHashIterations());
}
org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(java.lang.Object,java.lang.Object,int)
protectedHashhashProvidedCredentials(Objectcredentials,Objectsalt,inthashIterations){
StringhashAlgorithmName=assertHashAlgorithmName();
returnnewSimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
}
hashAlgorithmName:这个就是在凭证匹配器中定义的加密方式。
核心:进行加密的就是这个:
newSimpleHash(hashAlgorithmName,credentials,salt,hashIterations)
hashAlgorithmName:加密方式
credentials:表单提交的密码
salt:盐值
hashIterations:加密次数
用户注册的时候,向数据库存入密码的时候,可以使用此种方式对密码进行加密。
来看一个测试:
publicstaticvoidmain(String[]args){
StringhashAlgorithmName="MD5";
Objectcredentials="123456";
ByteSourcesalt=ByteSource.Util.bytes("abcd");
inthashIterations=1000;
SimpleHashsimpleHash=newSimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
System.out.println(simpleHash);
}
说了这么多,还没有说自定义认证方法的盐值是如何添加的:
com.nucsoft.shiro.shiro.MyRealm#doGetAuthenticationInfo
来看:
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
System.out.println("开始认证!");
UsernamePasswordTokenupToken=(UsernamePasswordToken)token;
Stringusername=upToken.getUsername();
Useruser=newUser();
user.setId(1000);
user.setUserName(username);
user.setPassword("42e56621cf3adc9ecc261936188d31d7");
user.getRoleNames().add("admin");
StringhashedCredentials=user.getPassword();
ByteSourcecredentialsSalt=ByteSource.Util.bytes("abcd");
StringrealmName=getName();
SimpleAuthenticationInfoinfo=newSimpleAuthenticationInfo(user,hashedCredentials,credentialsSalt,
realmName);
returninfo;
}
标红的地方就是加盐值后的处理方式。
在注册的时候,随机生成盐值,同用户信息存入数据库中。
7.关于细粒度的基于注解的授权和基于标签库的授权,本篇文章不进行说明。
二、总结
介绍了Spring环境下shiro的使用,包括环境的搭建,以及是如何配置的,自定义Realm可以完成自定义认证和自定义授权,也可以完成凭证匹配器的设置。
以及具体是怎么完成自定义认证和授权的,也将受保护的资源与访问的权限从xml文件中转到了java类中,为后续从数据库中读取提供了方便。
也介绍了加密的方式:加密类型,加密次数,加密盐值,以及具体是如何加密的。并没有讲明在真实项目中是如何使用的,以后有机会写文章来说明。
三、详细配置文件
1.web.xml
(1)shiro官方demo中的web.xml
?web.xml
(2)真实的环境的下web.xml
?web.xml
2.spring-shiro.xml
(1)官方demo中的?applicationContext.xml
?applicationContext.xml
(2)实验中的applicationContext-shiro.xml
?applicationContext-shiro.xml
?applicationContext-shiro.xml
3.springmvc.xml
?dispatcherServlet-servlet.xml
四、shiro中默认的过滤器名称以及使用
1.anno,匿名就可以访问,e:/admins/=anno
2.authc,认证后可以访问,e:/user/=authc
3.authcBasic,没有参数,表示需要通过httpBasic验证,如果不通过,跳转到登录页面。e:/user/=authcBasic
4.logout
5.noSessionCreation,阻止在请求期间创建新的会话,以保证无状态的体验。
6.perms,e1:/admins/=perms[user:add:],e2:/admins/users/=perms["user:add:,user:modify:"]
7.post,指定请求访问的端口,e:/admins/=port[8080]
8.rest,根据请求的方法,e:/admins/user/=perms[user.method],其中mothod为post,get,delete等。
9.roles,角色过滤器,判断当前用户是否拥有指定的角色。
10.ssl,没有参数,表示协议为https。
11.user,表示必须存在用户。
|
|