1. 前提條件
環境:jdk1.8、shiro1.4.0及以上版本、項目以 spring+shiro構建
工具:buji-pac4j-3.1.0-jar-with-dependencies.jar以及相關配置文件
從網上下載cas項目源碼
client為客戶端代碼,server為服務端代碼。
將buji-pac4j-3.1.0導入eclipse,eclipse須裝Maven插件,項目右鍵:
Run As --> Run Configurations
打開彈窗,Name一欄填buji-pac4j-3.1.0-jar-with-dependencies,
Base directory一欄選擇buji-pac4j-3.1.0的路徑,
Goals一欄填package,點擊Run即可生成buji-pac4j-3.1.0-jar-with-dependencies.jar
2. 導入相關文件
在項目的WEB-INF的lib下導入buji-pac4j-3.1.0-jar-with-dependencies.jar.
引入cas.properties(在編譯器中請放在resources下,tomcat中放在WEB-INF的class下)。
3. 修改相關聯的配置文件。
- 修改web.xml增加context-param
<!--本地登錄開關-->
<context-param>
<param-name>pac4jConfigLocation</param-name>
<param-value>classpath:cas.properties</param-value>
</context-param>
- 修改web.xml增加listener
<listener>
<listener-class>io.buji.pac4j.extension.listener.Pac4jServletContextListener</listener-class>
</listener>
放在Spring容器加載之后例如:
<!-- 加載Spring容器配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>io.buji.pac4j.extension.listener.Pac4jServletContextListener</listener-class>
</listener>
4 在shiro中追加相關配置
<!--追加 start-->
<bean id="annotationProxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="casRealm" class="io.buji.pac4j.extension.realm.SSOCasRealm">
<property name="cachingEnabled" value="false" />
<property name="authenticationCachingEnabled" value="false" />
<property name="authenticationCacheName" value="authenticationCache" />
<property name="authorizationCachingEnabled" value="false" />
<property name="authorizationCacheName" value="authorizationCache" />
</bean>
<!--追加 end-->
depends-on 需要Shiro生命周期處理器的id。例如:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
depends-on 需要的這個bean的id。
自定義casRealm實現認證和授權
<bean id="casRealm" class="io.buji.pac4j.extension.realm.SSOCasRealm">
<property name="cachingEnabled" value="false" />
<property name="authenticationCachingEnabled" value="false" />
<property name="authenticationCacheName" value="authenticationCache" />
<property name="authorizationCachingEnabled" value="false" />
<property name="authorizationCacheName" value="authorizationCache" />
</bean>
請繼承Pac4jRealm,bean的ID不要變例如:
public class Pac4jRealm extends io.buji.pac4j.realm.Pac4jRealm {
@Autowired
private UserService userService;
private String principalNameAttribute;
@Autowired
protected UserRoleService userRoleService;
@Autowired
protected OrganizationRoleService organizationRoleService;
@Autowired
protected ModuleService moduleService;
@Autowired
protected UserCasService userCasService;
// 是否啟用超級管理員
protected boolean activeRoot = true;
public String getPrincipalNameAttribute() {
return this.principalNameAttribute;
}
public void setPrincipalNameAttribute(String principalNameAttribute) {
this.principalNameAttribute = principalNameAttribute;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Pac4jToken token = (Pac4jToken)authenticationToken;
LinkedHashMap<String, CommonProfile> profiles = token.getProfiles();
String username = profiles.get("SSOCasClient").getId();
User user = userService.getByUsername(username);
ShiroUser shiroUser = new ShiroUser(user.getId(), user.getUsername(), user);
Object credentials = token.getCredentials();
return new SimpleAuthenticationInfo(shiroUser,credentials,getName());
}
/**
* 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
Collection<?> collection = principals.fromRealm(getName());
if (Collections3.isEmpty(collection))
{
return null;
}
ShiroUser shiroUser = (ShiroUser)collection.iterator().next();
/* SimplePrincipalCollection collection =(SimplePrincipalCollection) principals.getPrimaryPrincipal();
String userName =(String) collection.getPrimaryPrincipal();
User user = userService.getByUsername(userName);*/
List<UserRole> userRoles = userRoleService.find(shiroUser.getId());
List<OrganizationRole> organizationRoles =
organizationRoleService.find(shiroUser.getUser().getOrganization().getId());
//ShiroUser shiroUser = new ShiroUser(user.getId(), user.getUsername(), user);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(makePermissions(userRoles, organizationRoles, shiroUser));
return info;
}
private Collection<String> makePermissions(List<UserRole> userRoles, List<OrganizationRole> organizationRoles,
ShiroUser shiroUser)
{
// 是否啟用超級管理員
if (activeRoot)
{
// 為超級管理員,構造所有權限
if (userCasService.isSupervisor(shiroUser.getId()))
{
Collection<String> stringPermissions = Sets.newHashSet();
List<Module> modules = moduleService.findAll();
for (Module module : modules)
{
List<Permission> permissions = module.getPermissions();
// 默認構造CRUD權限
stringPermissions.add(module.getSn() + ":" + Permission.PERMISSION_CREATE);
stringPermissions.add(module.getSn() + ":" + Permission.PERMISSION_READ);
stringPermissions.add(module.getSn() + ":" + Permission.PERMISSION_UPDATE);
stringPermissions.add(module.getSn() + ":" + Permission.PERMISSION_DELETE);
for (Permission permission : permissions)
{
stringPermissions.add(module.getSn() + ":" + permission.getShortName());
}
}
//log.info("使用了超級管理員:" + shiroUser.getLoginName() + "登錄了系統。At " + new Date());
//log.info(shiroUser.getLoginName() + "擁有的權限:" + stringPermissions);
return stringPermissions;
}
}
Set<Role> roles = Sets.newHashSet();
for (UserRole userRole : userRoles)
{
roles.add(userRole.getRole());
}
for (OrganizationRole organizationRole : organizationRoles)
{
roles.add(organizationRole.getRole());
}
Collection<String> stringPermissions = Sets.newHashSet();
for (Role role : roles)
{
List<RolePermission> rolePermissions = role.getRolePermissions();
for (RolePermission rolePermission : rolePermissions)
{
Permission permission = rolePermission.getPermission();
stringPermissions.add(permission.getModule().getSn() + ":" + permission.getShortName());
}
}
// log.info(shiroUser.getLoginName() + "擁有的權限:" + stringPermissions);
return stringPermissions;
}
}
可以直接仿造自己項目中的realm寫,不一定要按照以上的Demo寫。
5 修改cas.properties
##cas服務前綴
sso.cas.server.prefixUrl=http://127.0.0.1:8081/cas
##cas服務登錄url
sso.cas.server.loginUrl=http://127.0.0.1:8081/cas/login
##cas客戶端回調地址
sso.cas.client.callbackUrl=http://127.0.0.1:8080/dts/callback?client_name=SSOCasClient
##cas服務端成功跳轉地址
sso.cas.client.successUrl=http://127.0.0.1:8080/dts/index
##cas登出地址
sso.cas.client.logoutUrl=http://127.0.0.1:8081/cas/logout
##本地登錄地址
sso.cas.client.nativeLoginUrl=http://127.0.0.1:8080/dts/login
##securityManagerId
securityManagerId=securityManager
##shiro配置filterChainDefinitions中定義的logout
logoutUrl=/logout
##shiro配置filterChainDefinitions中的最后一個匿名訪問地址
lastAnonUrl =/zui*/**
##shiro配置的org.apache.shiro.spring.web.ShiroFilterFactoryBean的id
originFilter=shiroFilter
##pac4j配置的org.apache.shiro.spring.web.ShiroFilterFactoryBean的id
ssoFilter=myfilter
##是否啟用開關
#1只能單點登錄 0全開啟 -1 只能本地登錄
loadFlag =1
這個Demo使用的cas服務端的登錄地址為:http://127.0.0.1:8081/cas/login
以上屬性名不要改變,只配置屬性值,ssoFilter這個屬性不用配置
6 服務端打war包及部署。
在IDEA里導入服務端項目cas\server\cas-4.2.1,
用Gradle插件刷新顯示所有子項目,找到cas-server-webapp,
點開cas-server-webapp --> Tasks --> build,先點擊clean再點擊war,
等待運行結束,在cas-server-webapp項目下build下libs里生成了war包。
將war包部署至服務器,訪問該項目即可。
7 常見問題解決。
1.項目部署之后, One or more listeners failed to start. Full details will be found in the appropriate container log file。
這個問題一般來是jar包沖突或版本不對。常見的就是Shiro的jar包版本過低。請升級到1.4或以上版本。還有一種情況,guava版本不同,可以嘗試下,刪除buji-pac4j-3.1.0-jar-with-dependencies.jar中的com文件夾。(刪除前請備份。)