本文目录导读:
探索 Shiro SSO 单点登录的实现代码奥秘
在当今的网络应用环境中,用户身份验证和授权是至关重要的环节,单点登录(SSO)技术的出现,为用户提供了更加便捷、高效的登录体验,同时也减轻了系统管理员的管理负担,Shiro 是一个强大的 Java 安全框架,它提供了丰富的功能和灵活的配置,使得实现 SSO 单点登录变得相对简单,本文将详细介绍如何使用 Shiro 实现 SSO 单点登录,并提供相应的代码示例。
Shiro 简介
Shiro 是一个功能强大的 Java 安全框架,它提供了认证、授权、会话管理等一系列安全功能,Shiro 的设计目标是提供简单易用的 API,同时支持多种安全策略和认证方式,在 Shiro 中,用户的身份信息被存储在 Subject 中,Subject 可以代表当前用户,通过 Shiro 的认证和授权机制,我们可以确保只有合法的用户才能访问系统的资源。
SSO 单点登录原理
SSO 单点登录的基本原理是通过一个中央认证服务器来管理用户的身份信息,当用户第一次登录系统时,系统会将用户的身份信息发送到中央认证服务器进行验证,如果验证通过,中央认证服务器会生成一个唯一的会话 ID,并将其返回给系统,系统会将这个会话 ID 存储在用户的浏览器中,并在后续的请求中携带这个会话 ID,以便中央认证服务器能够识别用户的身份。
Shiro SSO 单点登录实现步骤
1、搭建 Shiro 环境
我们需要搭建一个 Shiro 环境,可以通过 Maven 或 Gradle 等构建工具来管理项目的依赖关系,在项目中引入 Shiro 的相关依赖,
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.6.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.6.0</version> </dependency>
2、配置 Shiro 安全管理器
在 Shiro 中,安全管理器是负责管理安全相关的组件,例如认证、授权、会话管理等,我们需要配置一个 Shiro 安全管理器,以便使用 Shiro 的 SSO 单点登录功能,以下是一个简单的 Shiro 安全管理器配置示例:
import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean public Realm realm() { // 这里可以根据实际情况选择合适的 Realm 实现 return new MyRealm(); } @Bean public SessionManager sessionManager() { // 这里可以根据实际情况选择合适的 SessionManager 实现 return new DefaultWebSessionManager(); } @Bean public DefaultSecurityManager securityManager() { DefaultSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); securityManager.setSessionManager(sessionManager()); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } }
在上述配置中,我们首先定义了一个 Realm 实现,用于处理用户的认证和授权逻辑,我们定义了一个 SessionManager 实现,用于管理用户的会话,我们定义了一个 ShiroFilterFactoryBean,用于配置 Shiro 的过滤器链,在过滤器链中,我们定义了一些过滤规则,login 路径不需要认证,/logout 路径用于退出登录,/** 路径需要进行认证。
3、实现单点登录功能
在 Shiro 中,实现单点登录功能需要使用中央认证服务器,我们可以使用第三方的中央认证服务器,CAS(Central Authentication Service),也可以自己实现一个中央认证服务器,这里我们以 CAS 为例,介绍如何实现 Shiro 的单点登录功能。
我们需要在项目中引入 CAS 的相关依赖,
<dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-shiro</artifactId> <version>5.2.0</version> </dependency>
我们需要在 Shiro 的配置文件中配置 CAS 的相关参数,
import org.apereo.cas.client.authentication.AttributePrincipalResolver; import org.apereo.cas.client.session.SingleSignOutFilter; import org.apereo.cas.client.util.AssertionHolder; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Configuration public class ShiroCasConfig { @Bean public Filter singleSignOutFilter() { SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); singleSignOutFilter.setIgnoreInitConfiguration(true); singleSignOutFilter.setAuthenticationManager(shiroAuthenticationManager()); return singleSignOutFilter; } @Bean public Filter casFilter() { return new Cas30ProxyCallbackFilter() { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (request.getParameter("logout")!= null) { // 处理退出登录请求 Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { subject.logout(); } try { response.sendRedirect(getCasServerLogoutUrl()); } catch (IOException e) { e.printStackTrace(); } } else { super.doFilter(servletRequest, servletResponse, filterChain); } } @Override protected String getCasServerLoginUrl() { // 返回 CAS 登录地址 return "https://your-cas-server-url/login"; } @Override protected String getCasServerLogoutUrl() { // 返回 CAS 退出登录地址 return "https://your-cas-server-url/logout"; } @Override protected String getCasServerCallbackUrl() { // 返回 CAS 回调地址 return "http://your-app-url/callback"; } @Override protected boolean isAuthenticationRequest(HttpServletRequest request) { // 判断是否是登录请求 return "/login".equals(request.getServletPath()); } @Override protected boolean isServiceTicketRequest(HttpServletRequest request) { // 判断是否是服务票请求 return "ticket".equals(request.getParameter("ticket")); } @Override protected boolean isProxyCallback(HttpServletRequest request) { // 判断是否是代理回调请求 return "/callback".equals(request.getServletPath()); } @Override protected void internalRedirectForServiceTicket(HttpServletRequest request, HttpServletResponse response, String serviceTicket) throws IOException { // 处理服务票 String serviceUrl = getServiceUrl(request); response.sendRedirect(serviceUrl); } @Override protected void internalRedirectForProxy(HttpServletRequest request, HttpServletResponse response, String proxyUrl) throws IOException { // 处理代理 response.sendRedirect(proxyUrl); } @Override protected void internalRedirectForAuthenticationFailure(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理认证失败 response.sendRedirect(getLoginUrl()); } @Override protected void internalRedirectForAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理认证成功 response.sendRedirect(getSuccessUrl()); } @Override protected void internalRedirectForLogout(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理退出登录 response.sendRedirect(getLogoutUrl()); } @Override protected boolean handleLoginSuccess(HttpServletRequest request, HttpServletResponse response, Subject subject) throws ServletException, IOException { // 处理登录成功 AttributePrincipalResolver resolver = new AttributePrincipalResolver(); resolver.resolve(request, response, subject); return true; } @Override protected boolean handleLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Subject subject) throws ServletException, IOException { // 处理退出登录成功 AssertionHolder.clear(); return true; } }; } @Bean public org.apache.shiro.authc.AuthenticationManager shiroAuthenticationManager() { return new org.apache.shiro.web.mgt.DefaultWebSecurityManager(); } }
在上述配置中,我们首先定义了一个 SingleSignOutFilter,用于处理退出登录请求,我们定义了一个 Cas30ProxyCallbackFilter,用于处理 CAS 的登录、退出登录、服务票和代理回调请求,在 Cas30ProxyCallbackFilter 中,我们实现了一些回调方法,用于处理不同的请求,当用户登录成功后,我们会调用 handleLoginSuccess 方法,将用户的信息存储到 Shiro 的 Subject 中,当用户退出登录后,我们会调用 handleLogoutSuccess 方法,清除 Shiro 的 Subject 中的用户信息。
4、测试 SSO 单点登录功能
为了测试 SSO 单点登录功能,我们可以创建一个简单的 Web 应用,包含登录页面、用户信息页面和退出登录页面,在登录页面中,我们可以输入用户名和密码,然后点击登录按钮,系统会自动跳转到 CAS 登录页面,在 CAS 登录页面中,我们可以输入用户名和密码,然后点击登录按钮,系统会验证用户的身份,如果验证通过,系统会生成一个服务票,并跳转到我们的应用的回调页面,在回调页面中,我们可以获取服务票,并使用服务票去 CAS 服务器验证用户的身份,如果验证通过,系统会将用户的信息存储到 Shiro 的 Subject 中,并跳转到用户信息页面,在用户信息页面中,我们可以查看用户的信息,在退出登录页面中,我们可以点击退出登录按钮,系统会跳转到 CAS 退出登录页面,然后自动退出登录。
以下是一个简单的 Web 应用示例:
<!DOCTYPE html> <html> <head> <title>SSO 单点登录示例</title> </head> <body> <h2>SSO 单点登录示例</h2> <a href="/login">登录</a> <a href="/user">用户信息</a> <a href="/logout">退出登录</a> </body> </html>
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class HomeController { @RequestMapping("/") public String index() { return "index"; } @RequestMapping("/login") public String login() { return "login"; } @RequestMapping("/user") public String user() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { // 获取用户信息 return "user"; } else { return "redirect:/login"; } } @RequestMapping("/logout") public String logout() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { subject.logout(); } return "redirect:/"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String doLogin(String username, String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); return "login"; } return "redirect:/user"; } }
<!DOCTYPE html> <html> <head> <title>用户信息</title> </head> <body> <h2>用户信息</h2> <p>用户名:${username}</p> </body> </html>
import org.apache.shiro.SecurityUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import java.util.HashMap; import java.util.Map; @Controller public class UserController { @RequestMapping("/user") public ModelAndView user() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { // 获取用户信息 Map<String, Object> map = new HashMap<>(); map.put("username", subject.getPrincipal()); return new ModelAndView("user", map); } else { return new ModelAndView("redirect:/login"); } } }
在上述示例中,我们创建了一个简单的 Web 应用,包含登录页面、用户信息页面和退出登录页面,在登录页面中,我们可以输入用户名和密码,然后点击登录按钮,系统会自动跳转到 CAS 登录页面,在 CAS 登录页面中,我们可以输入用户名和密码,然后点击登录按钮,系统会验证用户的身份,如果验证通过,系统会生成一个服务票,并跳转到我们的应用的回调页面,在回调页面中,我们可以获取服务票,并使用服务票去 CAS 服务器验证用户的身份,如果验证通过,系统会将用户的信息存储到 Shiro 的 Subject 中,并跳转到用户信息页面,在用户信息页面中,我们可以查看用户的信息,在退出登录页面中,我们可以点击退出登录按钮,系统会跳转到 CAS 退出登录页面,然后自动退出登录。
通过以上步骤,我们成功地使用 Shiro 实现了 SSO 单点登录功能,在实现过程中,我们首先搭建了 Shiro 环境,然后配置了 Shiro 安全管理器和单点登录过滤器,我们创建了一个简单的 Web 应用,测试了 SSO 单点登录功能,在实际应用中,我们可以根据自己的需求,对 Shiro 进行进一步的配置和扩展,以满足不同的安全需求。
评论列表