配色: 字号:
Shiro —— Spring 环境下的使用
2016-09-23 | 阅:  转:  |  分享 
  
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,表示必须存在用户。























献花(0)
+1
(本文系白狐一梦首藏)