当前位置: 首页 >Java技术 > Shiro认证详解

Shiro认证详解

Shiro

shiro是一个java的安全框架
官网地址 http://shiro.apache.org/

Shiro综述

graph LR A1("CacheManager")-->B A2("Realms")-->B A3("UserDao")-->C A4("CredentialsMatcher")-->C A1-->C subgraph Shiro A("Subject(用户)")-->B("SecurityManager(安全管理器)") B-->C("Realm域") end
  • Subject:主体,代表了当前 “用户”
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;是 Shiro 的核心
  • Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

参考Shiro提供的JdbcRealm中源码的实现

//获取用户,其会自动绑定到当前线程Subject subject = SecurityUtils.getSubject();//构建待认证tokenUseamePasswordToken token = new UseamePasswordToken("zhang", "123");//登录,即身份验证subject.login(token);//判断是否已经认证subject.isAuthenticated()//登出subject.logout(token);
graph TBA(Realm)-->B(CachingRealm)B-->C(AuthenticatingRealm认证)C-->D(AuthorizingRealm授权)D-->E(自己实现的Realm)D-->E1(Shiro提供的JdbcRealm)E1-->F1(参考内部实现)E-->F("doGetAuthorizationInfo()")E-->G("doGetAuthenticationInfo()")style E fill:#f96

过滤器

认证拦截器

  • anon 匿名拦截器,不需要认证即可访问,如 /static/**=anon,/login=anon
  • authc 需要认证才可以访问,如/**=authc
  • user 用户已经身份验证 / 记住我登录的都可;示例 /**=user
  • logout 退出拦截器,如 /logout=logout

注意authc和user的区别

授权拦截器

  • roles 角色授权拦截器,验证用户是否拥有角色;如:/admin/**=roles[admin]
  • perms 权限授权拦截器,验证用户是否拥有所有权限;/user/**=perms["user:create"]

注解

  • @RequiresPermissions 验证权限
  • @RequiresRoles 验证角色
  • @RequiresUser 验证用户是否登录(包含通过记住我登录的)
  • @RequiresAuthentication 验证是否已认证(不含通过记住我登录的)
  • @RequiresGuest 不需要认证即可访问
//拥有ADMIN角色同时还要有sys:role:info权限@RequiresRoles(value={"ADMIN")@RequiresPermissions("sys:role:info")

整合Shiro

1. 配置SecurityManager

注入Realm和CacheManager(选)

 @Bean("securityManager")public org.apache.shiro.mgt.SecurityManager securityManager(ShrioRealm shrioRealm, PhoneRealm phoneRealm) {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//  //注入自定义myRealm//  defaultWebSecurityManager.setRealm(shrioRealm);//设置多个realm,用户名密码登录realm,手机号短信验证码登录realmList<Realm> realms = new ArrayList<>();realms.add(shrioRealm);realms.add(phoneRealm);defaultWebSecurityManager.setRealms(realms);retu defaultWebSecurityManager;}

2.实现Realm

注入密码验证器,设置是否启用缓存

/** * * 自定义realm * @author yuxf * @version 1.0 * @date 2020/12/21 16:10 */public class ShrioRealm extends AuthorizingRealm {@AutowiredTestShiroUserService userService;/** * 获取授权信息 * @param principals * @retu */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//从数据库取角色Set<String> roles = userService.getRoles();SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();//权限信息simpleAuthorizationInfo.addRoles(roles);simpleAuthorizationInfo.addStringPermission("user:create");retu  simpleAuthorizationInfo;}/** * 获取认证信息 * @param token * @retu * @throws AuthenticationException */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {if(token.getPrincipal()==null)retu null;String userName=token.getPrincipal().toString();//从数据库查询用户名String dbUser = userService.loadUserByUserName(userName);if(dbUser==null||"".equals(dbUser)) throw  new UnknownAccountException();//密码盐ByteSource salt = ByteSource.Util.bytes(userName);SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, "123456", salt,getName());retu simpleAuthenticationInfo;}} @Beanpublic ShrioRealm shrioRealm() {ShrioRealm shrioRealm = new ShrioRealm();//设置密码加密规则shrioRealm.setCredentialsMatcher(hashedCredentialsMatcher());retu shrioRealm;} /** * 凭证匹配器 * * @retu */@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); retu hashedCredentialsMatcher;}
/** * 注册时需要生成密码和密码盐存入数据库 * @author yuxf * @version 1.0 * @date 2020/12/22 17:01 */public class PasswordHelper {private static String algorithmName = "md5";private static final int hashIterations = 2;/** * 获取随机密码盐 * @retu */public  static String getSalt(){RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();String salt = randomNumberGenerator.nextBytes().toHex();retu  salt;}/** * 生成密码 * @param plainPassword 明文密码 * @param salt 密码盐 * @retu */public  static String getPassowrd(String plainPassword,String salt){String newPassword = new SimpleHash(algorithmName, plainPassword, salt, hashIterations).toHex();retu  newPassword;}}

3.配置LifecycleBeanPostProcessor

 /** * 配置LifecycleBeanPostProcessor 可以自动调用配置在Spring IOC容器中 Shiro Bean的生命周期方法 * @retu */@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {retu new LifecycleBeanPostProcessor();}

4.启动注解

/** * 配置注解生效 * * @retu */@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);retu defaultAdvisorAutoProxyCreator;}/** * 配置注解生效 * * @retu */@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") org.apache.shiro.mgt.SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();sourceAdvisor.setSecurityManager(securityManager);retu sourceAdvisor;}

5.配置ShiroFilter

ssm项目中坑

@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") org.apache.shiro.mgt.SecurityManager securityManager) {//shiro对象ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();bean.setSecurityManager(securityManager);bean.setLoginUrl("/shiro/login");bean.setSuccessUrl("/shrio/index");LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>();//认证顺序是从上往下执行。linkedHashMap.put("/logout", "logout");//在这儿配置登出地址,不需要专门去写控制器。linkedHashMap.put("/shiro/phoneLogin", "anon");linkedHashMap.put("/demo/**", "anon");linkedHashMap.put("/static/**", "anon");linkedHashMap.put("/shiro/anon", "anon");linkedHashMap.put("/**", "user");//需要进行权限验证bean.setFilterChainDefinitionMap(linkedHashMap);retu bean;}

SSM项目中web.xml中配置shiroFilter

<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->	<filter>		<filter-name>shiroFilter</filter-name>		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>		 <!-- 设置true由servlet容器控制filter的生命周期 -->		<init-param>			<param-name>targetFilterLifecycle</param-name>			<param-value>true</param-value>		</init-param> 		<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->		<init-param>			<param-name>targetBeanName</param-name>			<param-value>shiro</param-value>		</init-param>	</filter>

缓存

https://www.cnblogs.com/nuccch/p/8044226.html

思考:为什么Shiro要设计成既可以在Realm,也可以在SecurityManager中设置缓存管理器呢?

加密

https://www.cnblogs.com/cac2020/p/13850318.html

1. 注入HashedCredentialsMatcher实现(推荐)

需要自己编写加密帮助类生成密码和盐值,比较灵活

@Beanpublic ShrioRealm shrioRealm() {ShrioRealm shrioRealm = new ShrioRealm();//设置密码加密规则shrioRealm.setCredentialsMatcher(hashedCredentialsMatcher());retu shrioRealm;}  /** * 凭证匹配器 * * @retu */@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));retu hashedCredentialsMatcher;}

加密帮助类

/** * 注册时需要生成密码和密码盐存入数据库 * * @author yuxf * @version 1.0 * @date 2020/12/22 17:01 */public class PasswordHelper {private static String algorithmName = "md5";private static final int hashIterations = 2;/** * 获取随机密码盐 * * @retu */public static String getSalt() {RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();String salt = randomNumberGenerator.nextBytes().toHex();retu salt;}/** * 生成密码 * * @param plainPassword 明文密码 * @param salt  密码盐 * @retu */public static String getPassowrd(String plainPassword, String salt) {String newPassword = new SimpleHash(algorithmName, plainPassword, salt, hashIterations).toHex();retu newPassword;}}

2. 注入PasswordMatcher实现

  1. Shiro提供的PasswordService 相当于 密码帮助类,可用于生成密码和验证密码
  2. 如果使用公盐(hashService.setGeneratePublicSalt(true)),则必须设置HashFormat为Shiro1CryptFormat或不设置,默认为这个,否则无法保存盐值导致验证失败,密码加密结果如:$shiro1$MD5$3$QvLJZY8JiAJMnK9vRjlG6w==$jbNS0N/3fq2KUXufYwGwWA==,里面包含了加密的方法类型,哈希次数,盐值,加密结果,验证密码时会取出加密密码中的盐值来hash客户端的密码来验证密码是否正确
  3. 盐值保存在密码中,无需额外存储
@Beanpublic PhoneRealm phoneRealm() {PhoneRealm phoneRealm = new PhoneRealm();//PasswordMatcherPasswordMatcher passwordMatcher = new PasswordMatcher();passwordMatcher.setPasswordService(passwordService());phoneRealm.setCredentialsMatcher(passwordMatcher);retu phoneRealm;} @Beanpublic  PasswordService passwordService(){DefaultHashService hashService = new DefaultHashService();hashService.setHashIterations(3);hashService.setHashAlgorithmName("MD5");hashService.setGeneratePublicSalt(true);//设置HashServiceDefaultPasswordService passwordService = new DefaultPasswordService();passwordService.setHashService(hashService);// passwordService.setHashFormat(new HexFormat());retu  passwordService;}

多身份Realm认证

  1. (推荐)自定义AuthenticationToken并重写Realm的supports方法,来明确Real支持的Token

注意不要继承UseamePasswordToken

public class PhoneVcodeToken implements AuthenticationToken {private String phone;private String vcode;public  PhoneVcodeToken(String phone,String vcode){this.phone=phone;this.vcode=vcode;}@Overridepublic Object getPrincipal() {retu phone;}@Overridepublic Object getCredentials() {retu vcode;}}

Realm

public class PhoneRealm extends AuthorizingRealm {@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {retu null;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String userName = token.getPrincipal().toString();if (userName.equals("admin")) {//123456aretu new SimpleAuthenticationInfo(userName, "$shiro1$MD5$3$j8X4VX1f6T6zGiGEFIW5yA==$ipG89XmDquh++g5xXmV1dQ==", getName());} else {//123456retu new SimpleAuthenticationInfo(userName, "$shiro1$MD5$3$QvLJZY8JiAJMnK9vRjlG6w==$jbNS0N/3fq2KUXufYwGwWA==", getName());}}@Overridepublic boolean supports(AuthenticationToken token) {retu token instanceof PhoneVcodeToken;}}
  1. 自定义AuthenticationToken并加入类型参数,重写ModularRealmAuthenticator 在doAuthenticate()方法中根据类型来选择Realm
/** * @author chenzhi * @Description: 自定义当使用多realm时管理器 * @Date:Created: in 13:41 2018/8/13 * @Modified by: */public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { @Overrideprotected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {//先判断Realm是否为空assertRealmsConfigured();//强转为自定义的TokenMyUseamePasswordToken myUseamePasswordToken = (MyUseamePasswordToken) authenticationToken;//拿到登录类型String loginType = myUseamePasswordToken.getLoginType();//拿到所有Realm集合Collection<Realm> realms = getRealms();List<Realm> myrealms = new ArrayList<>();//遍历每个realm 根据loginType将对应的Reaml加入到myrealmsfor (Realm realm : realms) {//拿到Realm的类名 ,所以在定义Realm时,类名要唯一标识并且包含枚举中loginType某一个Type//注意:一个Realm的类名不能包含有两个不同的loginTypeif (realm.getName().contains(loginType)) {myrealms.add(realm);}}//判断是单Reaml还是多Realmif (myrealms.size() == 1) {retu doSingleRealmAuthentication(myrealms.iterator().next(), myUseamePasswordToken);} else {retu doMultiRealmAuthentication(myrealms, myUseamePasswordToken);}}}

认证流程

token=new UseamePasswordToken(userName,password)
graph TB subgraph Suject A1("Subject")--"subject = SecurityUtils.getSubject();"-->C1("token") B1(token)-->C1("subject.login(token)") end subgraph SecurityManager A2("securityManager.login(token)") B2("onSuccessfulLogin(token, info, loggedIn)") end subgraph ModularRealmAuthenticator A3("authenticate(token)") end subgraph Realm A4("getAuthenticationInfo(token)")--获取认证信息-->B4("doGetAuthenticationInfo(token)") B4--传入认证信息并验证密码-->C4("assertCredentialsMatch(token,info)") end subgraph CredentialsMatcher A5("doCredentialsMatch(token,info)") end C1-->A2 A2--this.authenticator-->A3 A3-->B2 A3--"this.getRealms()"-->A4 C4--"getCredentialsMatcher()"-->A5

作者:鱿鱼丝儿
来源链接:https://www.cnblogs.com/yuxin5156/p/14211913.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。





本文链接:https://www.javaclub.cn/java/117199.html

标签:Shiro
分享给朋友:

“Shiro认证详解” 的相关文章