博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
cas5.3.2单点登录-服务端集成shiro权限认证(五)
阅读量:3784 次
发布时间:2019-05-22

本文共 14258 字,大约阅读时间需要 47 分钟。

原文地址,转载请注明出处:      © 

所谓单点登录(SSO),只当企业用户同时访问多个不同(类型的)应用时,他们只需要提供自身的用户凭证信息(比如用户名/密码)一次,当用户在不同的应用间切换时,他们不用再重复地输入自身的用户凭证了。我的设计思路是SSO只做认证中心,各应用的授权在各自的服务做,比如 查看订单权限, 这个权限,它可能仅仅只是订单系统这个应用的权限。因此,授权应该在客户端做,本篇只是简单的介绍cas服务端与shiro 的集成, 只验证是否拥有角色,有角色就可以登录,没角色不可以登录。

这里有两种方式

第一种:一种是官网文档方式,我照着官网的文档搞了一遍,只是实现了,在配置文件中写死几个用户和权限,并没有进行数据库操作。

第二种:这种方式就需要用到我们前面讲的自定义验证方式。不懂得同学可以看一下之前的博客。

第一种方式:官网

参考官网

与shiro集成

pom添加依赖
org.apereo.cas
cas-server-support-shiro-authentication
${cas.version}
application.properties添加shiro配置
#整合shiro#允许登录的用户,必须要有以下角色,否则拒绝,多个逗号隔开cas.authn.shiro.requiredRoles=admin#允许登录的用户,必须要有以下权限,否则拒绝,多个逗号隔开cas.authn.shiro.requiredPermissions=userInfo:add,userInfo:view#shir配置文件位置cas.authn.shiro.location=classpath:shiro.ini#shiro name 唯一cas.authn.shiro.name=cas-shiro# 与Query Authentication一致的加密策略cas.authn.shiro.passwordEncoder.type=DEFAULTcas.authn.shiro.passwordEncoder.characterEncoding=UTF-8cas.authn.shiro.passwordEncoder.encodingAlgorithm=MD5
在resources下创建shiro.ini,并添加以下内容
[main]cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManagersecurityManager.cacheManager = $cacheManager[users]#密码123admin = e10adc3949ba59abbe56e057f20f883e, admin#不可登录,因为配置了需要角色admin#密码123456test = ed0290f05224a188160858124a5f5077, test[roles]admin = userInfo:*test = commit:*

关于ini的配置参考开涛博客:

INI配置文件一般适用于用户少且不需要在运行时动态创建的情景下使用。

测试

使用admin登录成功,使用test登录失败。

第二种方式:自定义登录验证集成shiro

整合过程中出现的异常

第一个异常:无法退出登录

cas服务端整合shiro之后,可以登陆,但是在做登出的时候报以下异常:

org.springframework.webflow.execution.ActionExecutionException: Exception thrown executing org.springframework.webflow.action.ViewFactoryActionAdapter@77b86c03 in state 'logoutView' of flow 'logout' -- action execution attributes were 'map[[empty]]'

这里写图片描述

原因:是使用了ShiroFilterFactoryBean,在cas服务端整合shiro的时候,我们不应该再配置这个Bean,也就是说,只用到了shiro的Subject.login();只做鉴权,不做其他退出之类的,退出还是走cas的默认登出。如果不配置该Bean 又会报找不到securityManager异常, ShiroFilterFactoryBean源代码如下:
这里写图片描述
上图中,我们看到
shiroFilterFactoryBean.setSecurityManager(securityManager);那如果不配置这个Bean,我们该怎么做呢?还记得上面的第一种官网的方法,我们可以看一下源码,发现其实也是自定义验证方式,在官网提供的ShiroAuthenticationHandler.java这个类中,发现了如下这个方法:
这里写图片描述
原来他是直接调用了SecurityUtils.setSecurityManager(securityManager);,我们也这样做,可以直接在配置securityManager的地方,如下:
这里写图片描述
或者还有另外一种方法,记得我们在学习shiro的时候,配置了一个MethodInvokingFactoryBean这个Bean 是Spring静态注入。配置下面的代码,等于调用了SecurityUtils.setSecurityManager(securityManager);

/** * Spring静态注入 * @return */@Beanpublic MethodInvokingFactoryBean getMethodInvokingFactoryBean(){    MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();    factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");    factoryBean.setArguments(new Object[]{securityManager()});    return factoryBean;}

然后就可以使用cas的登出功能了。

第二个错误:

登录时,认证信息无效,后台报以下异常:

java.lang.IllegalArgumentException: SessionContext must be an HTTP compatible implementation.
原因:使用的是DefaultWebSecurityManager 改为 DefaultSecurityManager 就可以了。
解决方法参考该

下面开始整合过程:

pom添加相关依赖
org.apereo.cas
cas-server-core-webflow
${cas.version}
org.apereo.cas
cas-server-core-authentication
${cas.version}
org.apereo.cas
cas-server-core-authentication-api
${cas.version}
org.apereo.cas
cas-server-webapp-config
${cas.version}
provided
com.alibaba
druid
1.0.28
org.apache.shiro
shiro-spring
1.4.0
org.apereo.cas
cas-server-support-generic
${cas.version}
自定义验证器ShiroAuthenticationHandler.java
package com.wangsaichao.cas.adaptors.generic;import com.wangsaichao.cas.service.RoleService;import com.wangsaichao.cas.service.UserService;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.subject.Subject;import org.apereo.cas.authentication.*;import org.apereo.cas.authentication.exceptions.AccountDisabledException;import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;import org.apereo.cas.authentication.principal.PrincipalFactory;import org.apereo.cas.services.ServicesManager;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import javax.security.auth.login.AccountLockedException;import javax.security.auth.login.AccountNotFoundException;import javax.security.auth.login.CredentialExpiredException;import javax.security.auth.login.FailedLoginException;import java.security.GeneralSecurityException;import java.util.Map;import java.util.Set;/** * @author: wangsaichao * @date: 2018/7/17 * @description: */public class ShiroAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
private static final Logger logger = LoggerFactory.getLogger(ShiroAuthenticationHandler.class); @Autowired private UserService userService; @Autowired private RoleService roleService; public ShiroAuthenticationHandler(String name,ServicesManager servicesManager,PrincipalFactory principalFactory,Integer order) { super(name, servicesManager, principalFactory, order); } @Override protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential transformedCredential,String originalPassword) throws GeneralSecurityException { try { UsernamePasswordToken token = new UsernamePasswordToken(transformedCredential.getUsername(),transformedCredential.getPassword()); if (transformedCredential instanceof RememberMeUsernamePasswordCredential) { token.setRememberMe(RememberMeUsernamePasswordCredential.class.cast(transformedCredential).isRememberMe()); } Subject currentUser = getCurrentExecutingSubject(); currentUser.login(token); checkSubjectRolesAndPermissions(currentUser); return createAuthenticatedSubjectResult(transformedCredential, currentUser); } catch (final UnknownAccountException uae) { throw new AccountNotFoundException(uae.getMessage()); } catch (final IncorrectCredentialsException ice) { throw new FailedLoginException(ice.getMessage()); } catch (final LockedAccountException | ExcessiveAttemptsException lae) { throw new AccountLockedException(lae.getMessage()); } catch (final ExpiredCredentialsException eae) { throw new CredentialExpiredException(eae.getMessage()); } catch (final DisabledAccountException eae) { throw new AccountDisabledException(eae.getMessage()); } catch (final AuthenticationException e) { throw new FailedLoginException(e.getMessage()); } } /** * Check subject roles and permissions. * 这只是举个简单的例子 进行对比,可以自己写 自己对应的逻辑 * * @param currentUser the current user * @throws FailedLoginException the failed login exception in case roles or permissions are absent */ protected void checkSubjectRolesAndPermissions(final Subject currentUser) throws FailedLoginException { //查询用户id, 也可以在登录成功之后,将id 放到session中,从session中获取,这里直接查库 Map
user = userService.findByUserName(String.valueOf(currentUser.getPrincipal())); //获取所有的用户角色 Set
allRoles = roleService.findAllRoles(); //根据id获取用户的角色,这里一个用户只对应一个角色 String userRole = roleService.findRolesByUserId(String.valueOf(user.get("uid"))); //判断如果有角色,就登陆成功 for (String role : allRoles){ if (role.equals(userRole)) { return; } } //否则抛出异常,也可以自定义异常,返回不同的提示 throw new FailedLoginException(); } /** * Create authenticated subject result. * * @param credential the credential * @param currentUser the current user * @return the handler result */ protected AuthenticationHandlerExecutionResult createAuthenticatedSubjectResult(final Credential credential, final Subject currentUser) { final String username = currentUser.getPrincipal().toString(); return createHandlerResult(credential, this.principalFactory.createPrincipal(username)); } /** * Gets current executing subject. * * @return the current executing subject */ protected Subject getCurrentExecutingSubject() { return SecurityUtils.getSubject(); }}
注册验证器并添加shiro配置ShiroAuthenticationConfiguration.java
package com.wangsaichao.cas.config;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.mgt.SecurityManager;import com.wangsaichao.cas.adaptors.generic.ShiroAuthenticationHandler;import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;import org.apereo.cas.authentication.AuthenticationHandler;import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;import org.apereo.cas.configuration.CasConfigurationProperties;import org.apereo.cas.services.ServicesManager;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.beans.factory.config.MethodInvokingFactoryBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author: wangsaichao * @date: 2018/7/16 * @description: shiro配置 */@Configuration("shiroAuthenticationConfiguration")@EnableConfigurationProperties(CasConfigurationProperties.class)public class ShiroAuthenticationConfiguration  implements AuthenticationEventExecutionPlanConfigurer {
@Autowired private CasConfigurationProperties casProperties; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Bean(name="securityManager") public SecurityManager securityManager() { DefaultSecurityManager securityManager = new DefaultSecurityManager(); //设置自定义realm. securityManager.setRealm(shiroRealm()); return securityManager; } @Bean public ShiroRealm shiroRealm(){ ShiroRealm shiroRealm = new ShiroRealm(); shiroRealm.setCachingEnabled(false); //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false shiroRealm.setAuthenticationCachingEnabled(false); //启用授权缓存,即缓存AuthorizationInfo信息,默认false shiroRealm.setAuthorizationCachingEnabled(false); return shiroRealm; } /** * Spring静态注入 * @return */ @Bean public MethodInvokingFactoryBean getMethodInvokingFactoryBean(){ MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean(); factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); factoryBean.setArguments(new Object[]{securityManager()}); return factoryBean; } @Bean public AuthenticationHandler shiroAuthenticationHandler() { ShiroAuthenticationHandler handler = new ShiroAuthenticationHandler(ShiroAuthenticationHandler.class.getSimpleName(), servicesManager, new DefaultPrincipalFactory(),10); return handler; } @Override public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) { // TODO Auto-generated method stub plan.registerAuthenticationHandler(shiroAuthenticationHandler()); }}
ShiroRealm.java
package com.wangsaichao.cas.config;import com.wangsaichao.cas.service.UserService;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import java.util.Map;/** * @author: wangsaichao * @date: 2018/5/10 * @description: 在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的 * 在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO. */public class ShiroRealm extends AuthorizingRealm {
@Autowired private UserService userService; /** * 验证用户身份 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取用户名密码 第一种方式 //String username = (String) authenticationToken.getPrincipal(); //String password = new String((char[]) authenticationToken.getCredentials()); //获取用户名 密码 第二种方式 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); Map
user = userService.findByUserName(username); //可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验 if (user == null) { throw new UnknownAccountException("用户名或密码错误!"); } //这里将 密码对比 注销掉,否则 无法锁定 要将密码对比 交给 密码比较器 在这里可以添加自己的密码比较器等 //if (!password.equals(user.getPassword())) {
// throw new IncorrectCredentialsException("用户名或密码错误!"); //} if ("1".equals(user.get("state"))) { throw new LockedAccountException("账号已被锁定,请联系管理员!"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, user.get("password"), getName()); return info; } /** * 授权用户权限 但是这个方法并不用,我们会在 ShiroAuthenticationHandler的 checkSubjectRolesAndPermissions 中单独去验证 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("查询权限方法调用了!!!"); //添加角色 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); return authorizationInfo; }}
加载该配置类

在resources\META-INF\spring.factories中配置该类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangsaichao.cas.config.ShiroAuthenticationConfiguration

测试

使用test登录,弹出锁定页面。

使用admin可以登录成功。
使用一个没有任何角色的账号,无法登陆,认证失败。

你可能感兴趣的文章
银行业务队列简单模拟(队列queue)
查看>>
MySql中的数据查询语言(DQL)三:连接查询
查看>>
MySql中的数据查询语言(DQL)五:union和limit
查看>>
数据操作语言(DML)一:插入数据insert、修改数据update、删除delete
查看>>
.properties 文件,.yml 文件 ,yaml文件语法学习
查看>>
jsp 的常用标签
查看>>
Listener 监听器
查看>>
SpringBoot自动配置原理
查看>>
IDEA连接mysql又报错设置时区!Server returns invalid timezone.
查看>>
员工管理系统二:首页和国际化实现
查看>>
员工管理系统四:员工列表实现
查看>>
员工管理系统五:增删改员工实现
查看>>
Redis的安装与卸载
查看>>
项目阶段五:验证码
查看>>
项目阶段五:购物车
查看>>
项目阶段六:订单模块的数据库准备与dao、service层
查看>>
项目阶段六:后台管理的订单模块
查看>>
练习——图书管理系统八(根据图书编号填充图书名称下拉控件和验证手机号)
查看>>
将windows下文件上传至服务器中
查看>>
正则表达式:贪婪模式与懒惰模式
查看>>