简介 网上最流行的两个Java安全框架就是Shiro和spring security。spring security功能更加强大,而Shiro是更加简单。这次就先来学习一下Shiro。首先我们来看看官网上对Shiro的描述。
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理。 使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
核心架构 在官网中的参考文档可以看到Shiro的框架架构图和一些解释。
SubjectSubject
表示需要认证的数据,可以理解为登陆的用户,一般有一个用户表示身份的信息和验证身份的密码,表示身份的信息一定要由唯一性。
Security Manager
Security Manager
是Shiro框架的核心,该对象会协调其内部的安全组件。
Realm
可以以称为域,充当Shiro和程序安全数据的桥梁,也就是匹配的数据,用来确定Subject登陆是否能够成功。
下面这张就是更加详细的架构图。
上面的部分其实就是上图的Subject
,可以Shiro是可以支持不同语言的服务的。
中间蓝色的就是框架的核心Security Manager
。
Authenticator(认证)
用来负责认证用户登陆是否成功。当用户尝试登陆时,会通过Authenticator
去与单个或多个的Realms
去验证该用户是否为合法用户。
Authenticator(授权)
Authenticator
是用来确定用户在应用程序中的访问控制。即表示该用户经过认证后,在程序中被允许做什么事情。
Session Manager
用来创建和管理用户Session
的生命周期,管理会话。SessionDAO 算是实现会话管理操作的接口。
Cache Manager
用来管理缓存,如果每次执行操作都要去认证授权,那会大幅降低效率,加入了缓存,那就可以不用每次都进行认证和授权了。
Cryptography
翻译为密码术或者称密码学。其实就是用来加密和解密的。
下面的部分就为Realms
。可以看到可以支持多种方式来验证用户的合法性。
认证 从上面的架构图的各部分的功能可以了解了用户认证的过程。
Subject
从应用程序过来寻求认证。Shiro将Subject
交给Security Manager
去管理。 然后就会交给Authenticator
去认证,Authenticator
又会从Realms
去获取安全数据与Subject
去比较,确认Subject
的合法性。 现在就来简单的实现一下这个认证的流程。
ini配置文件 首先是创建一个简单的Maven项目,并导入依赖。
1 2 3 4 5 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.7.0</version> </dependency>
在resources文件夹下创建shiro.ini
配置文件。
这里就是用户的数据,用户名为yww
,密码为1141
。
然后就是实现了。
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(new IniRealm("classpath:shiro.ini" )); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" ,"1141" ); try { System.out.println("认证状态--->" + subject.isAuthenticated()); subject.login(token); System.out.println("认证状态--->" + subject.isAuthenticated()); } catch (Exception e) { e.printStackTrace(); } } }
1 2 认证状态---false 认证状态---true
现在修改一下登陆用户的信息。
1 UsernamePasswordToken token = new UsernamePasswordToken("yw" ,"1141" );
1 2 3 4 5 6 7 8 9 认证状态---false org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@573f d745] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - yw, rememberMe=false ]. at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:184 ) at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273 ) at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198 ) at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106 ) at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275 ) at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260 ) at com.yww.test.main(test.java:31 )
发现了这个UnknownAccountException
异常,表示找不到该用户,即没有该用户。
在修改一下登陆用户的信息。
1 UsernamePasswordToken token = new UsernamePasswordToken("yww" ,"555" );
1 2 3 4 5 6 7 8 9 10 11 认证状态---false org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - yww, rememberMe=false ] did not match the expected credentials. at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:603 ) at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:581 ) at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180 ) at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273 ) at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198 ) at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106 ) at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275 ) at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260 ) at com.yww.test.main(test.java:31 )
会发现另一个异常IncorrectCredentialsException
,表示认证不通过,一般就是密码不正确。
常见的就是这两个异常了,现在完善一下认证的程序。
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.text.IniRealm;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(new IniRealm("classpath:shiro.ini" )); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" ,"555" ); try { System.out.println("认证状态---" + subject.isAuthenticated()); subject.login(token); System.out.println("认证状态---" + subject.isAuthenticated()); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } } }
自定义Realms 导入依赖后,创建Realms的实现类,继承AuthorizingRealm
,并重写其中的两个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CustomRealms extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); if ("yww" .equals(principal)) { SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"555" ,this .getName()); return simpleAuthenticationInfo; } return null ; } }
认证过程主要是doGetAuthenticationInfo
这个方法,上面的授权方法先不重写。
然后是测试类。
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(new CustomRealms()); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" , "555" ); try { System.out.println("认证状态---" + subject.isAuthenticated()); subject.login(token); System.out.println("认证状态---" + subject.isAuthenticated()); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } } }
加密 这里就使用到了三种加密方法,md5,salt和hash散列,具体是怎么加密的就不详细了解了,大概知道就好了。
MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。 salt就是使用随机的字符串加到密码后进行md5加密,加的位置不知道也会让解密困难。 Hash算法可以将一个数据转换为一个标志,这个标志和源数据的每一个字节都有十分紧密的关系。Hash算法还具有一个特点,就是很难找到逆向规律。 下面简单的测试下加密方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Encryption { public static void main (String[] args) { String password = "123456" ; Md5Hash md5Hash1 = new Md5Hash(password); System.out.println(md5Hash1.toHex()); Md5Hash md5Hash2 = new Md5Hash(password,"k*63" ); System.out.println(md5Hash2.toHex()); Md5Hash md5Hash3 = new Md5Hash(password, "k*63" , 1024 ); System.out.println(md5Hash3.toHex()); } }
1 2 3 e10adc3949ba59abbe56e057f20f883e a57ba7884ad33d967db809552d96c6f6 c9222f707633cc9a650286ca3bbadbe7
接下来就来测试一下如何使用加密。
首先也是先创建自定义的realms。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class CustomRealms extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); if ("yww" .equals(principal)) { SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"555" ,this .getName()); return simpleAuthenticationInfo; } return null ; } }
解决MD5加密,先创建一个hash凭证匹配器,在将该匹配器给realms。
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5" ); CustomRealms realms = new CustomRealms(); realms.setCredentialsMatcher(hashedCredentialsMatcher); securityManager.setRealm(realms); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" , "555" ); try { System.out.println("认证状态---" + subject.isAuthenticated()); subject.login(token); System.out.println("认证状态---" + subject.isAuthenticated()); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } } }
salt的处理,在匹配的时候加上salt就好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { String principal = (String) authenticationToken.getPrincipal(); if ("yww" .equals(principal)) { SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"a57ba7884ad33d967db809552d96c6f6" , ByteSource.Util.bytes("k*63" ) this .getName()); return simpleAuthenticationInfo; } return null ; }
hash散列的处理,设置匹配器的时候加上散列的次数。
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5" ); hashedCredentialsMatcher.setHashIterations(1024 ); CustomRealms realms = new CustomRealms(); realms.setCredentialsMatcher(hashedCredentialsMatcher); securityManager.setRealm(realms); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" , "555" ); try { System.out.println("认证状态---" + subject.isAuthenticated()); subject.login(token); System.out.println("认证状态---" + subject.isAuthenticated()); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } } }
授权 对于角色的授权 1 2 3 4 5 6 7 8 9 10 11 @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole("admin" ); return simpleAuthorizationInfo; }
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;import java.util.Arrays;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5" ); hashedCredentialsMatcher.setHashIterations(1024 ); CustomRealms realms = new CustomRealms(); realms.setCredentialsMatcher(hashedCredentialsMatcher); securityManager.setRealm(realms); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" , "555" ); try { subject.login(token); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } if (subject.isAuthenticated()) { System.out.println(subject.hasRole("admin" )); System.out.println(subject.hasRoles(Arrays.asList("admin" ,"user" ))); boolean [] booleans = subject.hasRoles(Arrays.asList("admin" , "user" )); } } }
对决资源的授权 这里涉及到权限字符串,大概了解一下字符串的写法和格式。
资源标识符:操作:资源类型
可以使用*号代表所有
admin:create:*
这个权限代表当前角色对admin中所有资源由创建的权限
admin:*:01
这个权限代表当前角色对admin中01用户存在所有权限
1 2 3 4 5 6 7 8 9 10 11 @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermission("admin:create:*" ); return simpleAuthorizationInfo; }
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 package com.yww;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;public class test { public static void main (String[] args) { DefaultSecurityManager securityManager = new DefaultSecurityManager(); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5" ); hashedCredentialsMatcher.setHashIterations(1024 ); CustomRealms realms = new CustomRealms(); realms.setCredentialsMatcher(hashedCredentialsMatcher); securityManager.setRealm(realms); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("yww" , "555" ); try { subject.login(token); } catch (IncorrectCredentialsException e) { System.out.println("认证不通过,密码不正确" ); } catch (UnknownAccountException e) { System.out.println("认证不通过,该用户不存在" ); } catch (Exception e) { e.printStackTrace(); } if (subject.isAuthenticated()) { System.out.println(subject.isPermitted("admin:create:*" )); System.out.println(subject.isPermittedAll("admin:create:*" ,"admin:update:*" )); boolean [] booleans = subject.isPermitted("admin:create:*" ,"admin:create:01" ); } } }
参考链接