基于SpringBoot Shiro CAS单点登录实现
来源:互联网 发布:python hdfs3 编辑:程序博客网 时间:2024/06/05 11:31
Shiro CAS 单点登录实现
2017年12月14日12时 上海浦东 天气阴
博客状态: 已完成
进度:100%
计划完成:2017年12月16日
本文主要描述基于Spring Boot , Shiro框架的CAS单点登录实现
大致步骤如下,搭建完成后按思路写的,可能存在一些遗漏
作为一个对Shiro权限控制,SpringBoot都一知半解的程序员
突然有一天,领导要你基于SpringBoot , Shiro的二个系统做一个单点登录.
目录
- Shiro CAS 单点登录实现
- 目录
- 一 简单思想概念理解
- 二 Shiro CAS 服务搭建
- 1 Http协议修改
- 2 配置JDBC数据源
- 3 自定义密码校验
- 三 Shiro CAS 登录验证
- 1 反编译检查
- 2 工程名称指定
- 3 登录登出
- 四 Shiro CAS 与 业务系统 集成
- 1 依赖包
- 2 变量配置
- 3 自定义实现CasRealm
- 4 配置Shiro Cas对象
- 五 单点登录验证
一 : 简单思想概念理解
1,为什么要用Shiro CAS?
Shiro权限控制易于使用,且功能齐全,Shiro既然有自己的单点登录服务,为什么还要去考虑其他的.2,Shiro CAS单点登录流程是怎样的?
网上流程图例很多,作为一个新手,嗯?看不懂,很正常
个人简单理解 :
1,搭建CAS服务后,会有一个CAS的登录页(账号/密码),需要配置我们的数据源 和 用户名查询密码的SQL
2,之后我们在业务系统中需要配置
2.1, Shiro Cas的请求拦截过滤,将某种请求(如登录请求:login.html)重定向到这个CAS的登录页
2.2, Shiro Cas验证成功/失败后跳转地址(如成功:home.html,如失败:error.html)
2.3, 实现一个CasRealm接口的类 (MyCasRealm.java), 重写二个方法
2.4 , 其他配置不一 一说明
3, 当以上配置完成后,当你请求业务系统登录页时,会被重定向到Shiro CAS服务登录页,输入账号密码,CAS服务会基于你配置的数据源 通过 配置的SQL用用户名去查询密码,再用你输入的密码和查询的密码相比较,如果一致说明登录成功,成功之后便会调用业务系统的MyCasRealm.java类的重写方法,参数为(Token)对象(包含用户信息),你再通过Token传过来的用户名去查询出用户信息,保存到Shiro的Subject对象中,供业务系统使用,最终再跳转到你配置的成功路径。
二 : Shiro CAS 服务搭建
- 下载CAS服务源码: GitHub CAS
- 使用开发工具构建Maven CAS工程,博主使用IDEA工具
- 源码中有25个模块,其中只需要以下二个模块可以打包即可
CAS WEB服务模块:cas-server-webapp
自定义密码验证模块 : cas-server-support-jdbc
切记使用1.7 JDK,1.8 JDK 编译有问题
打包经常会遇见些编译的问题,篇幅有限自行解决吧
遇到一些插件异常 : 注释插件,禁止控制台输出校验:注释插件
提示一下 :
如果【CAS WEB服务】编译受限 ,War包可以去网上下载
但是如果你要用到【自定义密码】则需要对 cas-server-support-jdbc 模块做修改,打包放入CAS WEB的War包中
2.1 Http协议修改
默认Https协议,不然会有其他繁琐配置.
配置:不需要HTTPS安全验证
文件 cas-4.0.0-RC3\cas-server-webapp\src\main\webapp\WEB-INF\deployerConfigContext.xml
p:requireSecure=”false”
<bean id="proxyAuthenticationHandler"class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" p:requireSecure="false" />
配置:不需要安全cookie
文件 cas-4.0.0-RC3\cas-server-webapp\src\main\webapp\WEB-INF\spring-configuration\
ticketGrantingTicketCookieGenerator.xml
p:cookieSecure=”false”
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator" p:cookieSecure="false" p:cookieMaxAge="-1" p:cookieName="CASTGC" p:cookiePath="/cas" />
配置:不需要安全cookie
文件 cas-4.0.0-RC3\cas-server-webapp\src\main\webapp\WEB-INF\spring-configuration\warnCookieGenerator.xml
p:cookieSecure=”false”
<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator" p:cookieSecure="false" p:cookieMaxAge="-1" p:cookieName="CASPRIVACY" p:cookiePath="/cas" />
2.2 配置JDBC数据源
MYSQL数据源
文件 cas-4.0.0-RC3\cas-server-webapp\src\main\webapp\WEB-INF\deployerConfigContext.xml
新增如下代码段
<!--注释默认用户密码--> <!--自定义用户密码在这--> <!--<bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="casuser" value="Mellon"/> <entry key="admin" value="admin"/> </map> </property> </bean>--> <!-- 设置密码的加密方式,这里使用的是MD5加密 --> <bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" c:encodingAlgorithm="MD5" p:characterEncoding="UTF-8" /> <!-- 自定义SQL, 自定义查询密码得SQL,默认逻辑会使用用户名查询密码,与输入密码匹配 --> <!-- 当你数据库存储的密码为密文的时候,默认的对比逻辑不适用,就需要改↓class中指定类的代码了,稍后说明 --> <bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler" p:dataSource-ref="dataSource" p:passwordEncoder-ref="passwordEncoder" p:sql="select password from info_user where user_name = ? and del_flag=0" /> <!-- 设置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource. DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/databaseName?useUnicode=true&characterEncoding=utf-8"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
2.3 自定义密码校验
文件 cas-4.0.0-RC3\cas-server-support-jdbc\src\main\java\ org\jasig\cas\adaptors\jdbc\
QueryDatabaseAuthenticationHandler.java
该文件便是1.1自定义SQL指定的类
源码:如此简单的代码,相信你一看也就指定如何改了,结合你业务逻辑改,备注有描述,不再说明
public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { @NotNull private String sql; /** {@inheritDoc} */ @Override protected final Principal authenticateUsernamePasswordInternal(final String username, final String password) throws GeneralSecurityException, PreventedException { //页面输入的密码经过配置的加密格式传入,密文解码原始字符 final String encryptedPassword = this.getPasswordEncoder().encode(password); try { //this.sql为自定义查询SQL,username为查询用户密码的条件 final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username); //如果输入与查询不一致则抛出异常 if (!dbPassword.equals(encryptedPassword)) { throw new FailedLoginException("Password does not match value on record."); } } catch (final IncorrectResultSizeDataAccessException e) { if (e.getActualSize() == 0) { throw new AccountNotFoundException(username + " not found with SQL query"); } else { throw new FailedLoginException("Multiple records found for " + username); } } catch (final DataAccessException e) { throw new PreventedException("SQL exception while executing query for " + username, e); } //校验成功返回的对象,这个对象如果返回表示验证通过,将会调用业务系统的接口,进入自定义的CasRealm类的方法实现 return new SimplePrincipal(username); } /** * @param sql The sql to set. */ public void setSql(final String sql) { this.sql = sql; }}
注意:不要忘记在Cas Web服务中加入JDBC连接驱动 和 对 cas-server-support-jdbc 的依赖
//这个jdbc的依赖,tomcat运行好像是从模块里去拉,打包的jar去公网拉?不知道什么鬼,记得多检查这个包是不是正确就好了,如果不是的话就单独对这模块打包放到CAS WEB的War包的lib中去吧<dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-support-jdbc</artifactId> <version>${project.version}</version> <scope>compile</scope></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version></dependency>
三 : Shiro CAS 登录验证
如果你是在IDEA中做修改的话,那么我们就可以很方便的通过如下方法测试了
3.1 反编译检查
记得反编译检查这个类是不是正确
3.2 工程名称指定
指定 TOMCAT 工程名称,相当于对War包命名,这关系请求路径的问题
登录界面是这个,红框总有些红色的告警提示 和 一些国家的语言切换 ,被我干掉了 , 所以看起来比较整洁些
3.3 登录/登出
输入用户密码之后,如果登录成功了,说明配置的数据源成功了
这个界面只是测试服务是否启动,数据源连接是否正确,用户密码是否正确
当然这个界面做登录页面实在丑,而你需要把你的登录页面迁过来,表单,请求不变.
登录/登出
接下来开始和业务系统联调了,如何让业务系统与Cas服务结合使用
四 : Shiro CAS 与 业务系统 集成
4.1 依赖包
<!-- 业务系统加入如下依赖包 --><!-- shiro 和 cas服务的版本 关系应该不大吧,我没有去找过这二者的版本关系,使用起来并没有问题 --><!-- shiro集成到spring boot --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-aspectj</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>1.2.4</version> </dependency>
4.2 变量配置
文件 resources/application.properties
说明 这个文件应该是springboot使用的一些变量配置吧
#配置CAS服务地址 和 你业务系统地址,4.4 会使用到,上面的cas路径改cas-server了shiro.cas=http://127.0.0.1:8080/cas-servershiro.server=http://localhost:8086/shmetro
4.3 自定义实现CasRealm
//必须是CasRealmpublic class MyRealm extends CasRealm { private static Logger logger = LoggerFactory.getLogger(MyRealm.class); @Autowired private IInfoUserMapper infoUserMapper; @Autowired private IInfoAuthorityMapper infoAuthorityMapper; /** * 单Cas服务登录校验通过后,便会调用这个方法,并携带用户信息的Token参数 * 假设只要是有Token过来,就说明是有效的登录用户,不再对密码等做校验 * 方法名称 : doGetAuthenticationInfo * 功能描述 : 验证当前登陆的Subject * @param authcToken 当前登录用户的token * @return 验证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { AuthenticationInfo token = super.doGetAuthenticationInfo(authcToken); String userName =(String) token.getPrincipals().getPrimaryPrincipal(); logger.info("当前Subject时获取到用户名为" + userName); //根据用户名,查找用户信息 InfoUserBean user = loginUser(userName); if (user != null) { //user字符应该是固定写法 SecurityUtils.getSubject().getSession().setAttribute("user", user); } //这个token返回后便会进入配置中的成功路径 return token; } /** * 这里应该是请求用户的权限的方法,页面中 <shiro:hasRole name="ROLE_ADMIN"> 等类似的权限标签才会请求的方法,迁移过来业务相关代码,不解释了. * 方法名称 : doGetAuthorizationInfo * 功能描述 : 获取登录用户的权限信息 * @param principals 登录用户信息 * @return 用户权限信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { InfoUserBean user = CommonUtil.getCurrentUser(); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<AuthorityTreeBean> list; if (user.getId() == 1) { list = infoAuthorityMapper.selectAll(1,6); } else { list = infoAuthorityMapper.selectAuthorityByUserId(user.getId(), 1, 6); } List<String> permissions = new ArrayList<>(); for (AuthorityTreeBean authority : list) { permissions.add(authority.getAuthCode()); } info.addStringPermissions(permissions); return info; } else { return null; } } /** * 方法名称 : loginUser * 功能描述 : 登陆用户信息 * @param userName 用户名 * @return 用户信息 */ public InfoUserBean loginUser(String userName) { //查询用户信息 InfoUserBean userBean = infoUserMapper.selectByName(userName); String pass = userBean.getPassword(); //这里是对数据库提取的密码做加密操作,业务逻辑不必深究 Object[] result = DataConvertUtil.getPassAndSaltByte(userBean.getPassword()); String passwordHexStr = Hex.encodeHexString((byte[]) result[1]); userBean.setPassword(passwordHexStr); return userBean; }}
4.4 配置Shiro Cas对象
之前用SpringMVC框架,Bean的配置都是在XML中, SpringBoot建议这种方式,如果不是SpringBoot框架,你也可以把这些逻辑抽到Xml中去
* 你可以直接复制过去,在我注释的地方改动下即可 *
import com.shmetro.realm.MyRealm; //记住这个类,自己实现的import org.apache.shiro.cache.MemoryConstrainedCacheManager;import org.apache.shiro.cas.CasFilter;import org.apache.shiro.cas.CasSubjectFactory;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.filter.authc.LogoutFilter;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.filter.DelegatingFilterProxy;import javax.servlet.Filter;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;@Configurationpublic class ShiroConfig { //路径不能改 private static final String casFilterUrlPattern = "/shiro-cas"; @Bean public MyRealm getShiroRealm(@Value("${shiro.cas}") String casServerUrlPrefix, @Value("${shiro.server}") String shiroServerUrlPrefix) { //将MyRealm改成你自己的类,其他不动 MyRealm casRealm = new MyRealm(); casRealm.setCasServerUrlPrefix(casServerUrlPrefix); casRealm.setCasService(shiroServerUrlPrefix + casFilterUrlPattern); return casRealm; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); filterRegistration.addInitParameter("targetFilterLifecycle", "true"); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/*"); return filterRegistration; } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Value("${shiro.cas}") String casServerUrlPrefix,@Value("${shiro.server}") String shiroServerUrlPrefix) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(getShiroRealm(casServerUrlPrefix,shiroServerUrlPrefix)); securityManager.setCacheManager(new MemoryConstrainedCacheManager()); securityManager.setSubjectFactory(new CasSubjectFactory()); return securityManager; } //按你业务修改 //anon表示不过滤 //casFilter自定义过滤器:验证成功跳转地址/验证失败跳转地址 //logout:自定义过滤器:过滤单点登录退出请求 private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) { Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //你需要CAS校验的请求 filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter"); //你需要CAS校验的请求 filterChainDefinitionMap.put("/login.html", "casFilter"); //不需要拦截的静态文件请求 filterChainDefinitionMap.put("/static", "anon"); //单点登录退出请求拦截 filterChainDefinitionMap.put("/logout","logout"); //不过滤其他业务系统请求 filterChainDefinitionMap.put("/templates/*", "anon"); //不过滤其他业务系统请求 filterChainDefinitionMap.put("/iserver/services/*", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * 定义 CAS Filter */ @Bean(name = "casFilter") public CasFilter getCasFilter(@Value("${shiro.cas}") String casServerUrlPrefix, @Value("${shiro.server}") String shiroServerUrlPrefix) { CasFilter casFilter = new CasFilter(); casFilter.setName("casFilter"); casFilter.setEnabled(true); //校验失败地址,这里失败继续重定向单点登录界面 String failUrl = casServerUrlPrefix + "/login?service=" + shiroServerUrlPrefix + casFilterUrlPattern; //校验成功地址,登录成功后重定向的地址 String successUrl = shiroServerUrlPrefix + "/templates/main.jsp"; casFilter.setFailureUrl(failUrl); casFilter.setSuccessUrl(successUrl); return casFilter; } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,CasFilter casFilter,@Value("${shiro.cas}") String casServerUrlPrefix,@Value("${shiro.server}") String shiroServerUrlPrefix) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); String loginUrl = casServerUrlPrefix + "/login?service=" + shiroServerUrlPrefix + casFilterUrlPattern; shiroFilterFactoryBean.setLoginUrl(loginUrl); shiroFilterFactoryBean.setSuccessUrl("/"); Map<String, Filter> filters = new HashMap<>(); filters.put("casFilter", casFilter); LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setRedirectUrl(casServerUrlPrefix + "/logout?service=" + shiroServerUrlPrefix); filters.put("logout",logoutFilter); shiroFilterFactoryBean.setFilters(filters); loadShiroFilterChain(shiroFilterFactoryBean); return shiroFilterFactoryBean; }}
五 : 单点登录验证
- 基于SpringBoot Shiro CAS单点登录实现
- Shiro & CAS 实现单点登录
- Shiro & CAS 实现单点登录
- Springboot+shiro单点登录实现
- 基于CAS的单点登录SSO[5]: 基于Springboot实现CAS客户端的前后端分离
- shiro结合cas实现单点登录
- spring + shiro + cas 实现sso单点登录
- spring + shiro + cas 实现sso单点登录
- spring + shiro + cas 实现sso单点登录
- cas shiro spring实现单点登录
- spring + shiro + cas 实现sso单点登录
- shiro-cas 单点登录
- 基于CAS SHIRO LDAP 的SSO单点登录
- shiro 集成cas单点登录
- Liferay5.2.3基于CAS实现单点登录
- Liferay基于CAS实现单点登录说明
- SpringBoot整合cas单点登录
- 【cas基础】SSO基于cas实现单点登录
- 总线错误和段错误
- C#游戏开发基础01
- 词向量模型扩展
- Visual Studio 2013下调试dll的问题
- streamsets安装部署
- 基于SpringBoot Shiro CAS单点登录实现
- 使用Anaconda安装scrapy和ImportError: DLL load failed: 操作系统无法运行 %1决解方案
- 软件调试笔记68
- golang JSON的使用
- Tomcat系列—JDK安装(bin)
- 手把手教你制作 中英文 词云
- css 相关属性
- 软件调试笔记69
- Yarn_基础库