原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/81149019 ©王赛超
所谓单点登录(SSO),只当企业用户同时访问多个不同(类型的)应用时,他们只需要提供自身的用户凭证信息(比如用户名/密码)一次,当用户在不同的应用间切换时,他们不用再重复地输入自身的用户凭证了。我的设计思路是SSO只做认证中心,各应用的授权在各自的服务做,比如 查看订单权限, 这个权限,它可能仅仅只是订单系统这个应用的权限。因此,授权应该在客户端做,本篇只是简单的介绍cas服务端与shiro 的集成, 只验证是否拥有角色,有角色就可以登录,没角色不可以登录。
第一种:一种是官网文档方式,我照着官网的文档搞了一遍,只是实现了,在配置文件中写死几个用户和权限,并没有进行数据库操作。
第二种:这种方式就需要用到我们前面讲的自定义验证方式。不懂得同学可以看一下之前的博客。
https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html#shiro-authentication
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-support-shiro-authenticationartifactId>
<version>${cas.version}version>
dependency>
#整合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=DEFAULT
cas.authn.shiro.passwordEncoder.characterEncoding=UTF-8
cas.authn.shiro.passwordEncoder.encodingAlgorithm=MD5
[main]
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
[users]
#密码123
admin = e10adc3949ba59abbe56e057f20f883e, admin
#不可登录,因为配置了需要角色admin
#密码123456
test = ed0290f05224a188160858124a5f5077, test
[roles]
admin = userInfo:*
test = commit:*
关于ini的配置参考开涛博客:http://jinnianshilongnian.iteye.com/blog/2020820
INI配置文件一般适用于用户少且不需要在运行时动态创建的情景下使用。
使用admin登录成功,使用test登录失败。
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
*/
@Bean
public 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 就可以了。
解决方法参考该https://www.cnblogs.com/ningheshutong/p/6478080.html
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-webflowartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-authenticationartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-authentication-apiartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-webapp-configartifactId>
<version>${cas.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.28version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-support-genericartifactId>
<version>${cas.version}version>
dependency>
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();
}
}
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());
}
}
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可以登录成功。
使用一个没有任何角色的账号,无法登陆,认证失败。