基于SpringSecurityOAuth2实现单点登录, 简单示例用于学习SpringSecurityOAuth2. 解决了遇到的所有SpringSecurityOAuth2的坑

概述

基于SpringSecurityOAuth2实现单点登录, 简单示例用于学习SpringSecurityOAuth2. 解决了遇到的所有SpringSecurityOAuth2的坑。添加了注销登录,扩展了短信登录, 验证码等功能,实现了Vue前后端分离情况下的OAuth2单点登录。 麻雀虽小五脏俱全,满足企业级应用.

详细

一、需求(要做什么)

1.基于SpringSecurityOAuth2实现单点登录,实现基于SpringSecurityOAuth2的客户端对接认证中心, 实现基于OAuth2协议的原始方法对接认证中心, 基于vue前后端分离的方式对接认证中心。

2.编写基于oauth2认证的4种认证模式的demo, 让初学者一看就懂。 

3.实现登陆,登录通过账户密码登录, 也可通过手机验证码登录, 实现记住我,十天免登陆功能。

4.实现注销登录。

二、理论概述

        OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。 这是一个标准, spring也推荐也默认采用这种登录授权的模式, 所以跟着spring来,方向不会错。

        OAuth 的核心就是向第三方应用颁发令牌,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。

        OAuth 2.0 规定了四种获得令牌的流程:

            authorization code(授权码模式)

            implicit(简化模式)

            resource owner password credentials(密码模式)

            client credentials(客户端模式)

        授权码模式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用, 目前阿里,腾讯等大型互联网公司,甚至一些政府项目等都用这种方式, 看来不学不行了。

        

三、代码分析

1.首先引入pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
 
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
 
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
</dependency>

2.  认证中心的核心代码

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     
     
    @Autowired
    private DataSource dataSource;
     
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl  tokenRepository=new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //第一次创建开启
        //tokenRepository.setCreateTableOnStartup(true); 
        return tokenRepository;
    }
     
    @Autowired
    SsoUserDetailsServiceImpl  myUserDetailsService;
     
    @Autowired
    SsoSavedRequestAwareAuthenticationSuccessHandler  myOwnSavedRequestAwareAuthenticationSuccessHandler;
     
    @Autowired 
    SsoAuthenticationFailureHandler  myAuthenticationFailureHandler;
     
    @Autowired 
    SsoLogoutSuccessHandler  myLogoutSuccessHandler;
     
    @Autowired
    ValidateCodeFilterSecurityConfiguration  validateCodeFilterSecurityConfiguration;
     
    @Autowired
    LoginFormSecurityConfiguration  loginFormSecurityConfiguration;
     
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
         //跨站请求伪造
        http.csrf().disable();
         //解决iframe问题
        http.headers().frameOptions().disable();
            
                http
                .apply(validateCodeFilterSecurityConfiguration)
                .and()
               // .addFilterBefore(parametersFilter, UsernamePasswordAuthenticationFilter.class)
              //表单登录
                .formLogin()   
                    //自定义登陆的页面
                    .loginPage("/authentication/require")   
                    //指定登陆action 的 url
                    .loginProcessingUrl("/authentication/form")  
                    //登录成功处理
                    .successHandler(myOwnSavedRequestAwareAuthenticationSuccessHandler)  
                    //登出失败处理
                    .failureHandler(myAuthenticationFailureHandler)  
                    .and()
                    .rememberMe()
                    .tokenRepository(persistentTokenRepository())
                    .tokenValiditySeconds(3600)
                    .userDetailsService(myUserDetailsService)
                    .and()
                    //请求认证模块   所有请求都需要认证 
                    .authorizeRequests()     
                    .antMatchers("/authentication/require",
                            "/authentication/form",
                            "/logout",
                            "/login",
                            "/login/time",
                            "/getBackPasswordIndex",
                            "/register",
                            "/code/image",
                            "/code/sms",
                            "/error",
                            "/oauth/exit",
                            "/oauth/authorize/**",
                            "/oauth/token/**",
                            "/auth/oauth/token/**",
                            "/oauth/check_token/**",
                            "/oauth/confirm_access/**",
                            "/oauth/error/**",
                            "/images/**", 
                            "/plugin/**", 
                            "/js/**", 
                            "/css/**").permitAll() 
                   //所有请求认证
                    .anyRequest().authenticated()  
                    .and()
                    .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessHandler(myLogoutSuccessHandler)
                    .and()
                    .apply(loginFormSecurityConfiguration); 
                     
    }
     
 
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
     
     
    /**
     * session过期
     * @return
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}
 /**
     * DefaultTokenServices 实现了ConsumerTokenServices接口, revokeToken方法定义了删除token的方法
     */
    @Autowired
    @Qualifier("consumerTokenServices")
    private ConsumerTokenServices consumerTokenServices;
 
     
    @RequestMapping("/remove/token")
    public void removeToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String tokenValue = request.getParameter("access_token");
        String redirectUri = request.getParameter("redirect_uri");
        consumerTokenServices.revokeToken(tokenValue);
         
        // 清除记住我的数据
        request.getSession().invalidate();
         
        response.sendRedirect(redirectUri);
    }

3. 客户端的核心代码

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     
    @Autowired
    MyLogoutSuccessHandler  myLogoutSuccessHandler;
     
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         
         //跨站请求伪造
        http.csrf().disable();
         //解决iframe问题
        http.headers().frameOptions().disable();
 
        http.antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/login**","/logout/**","/js/**","/outer/**").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(myLogoutSuccessHandler);
 
        super.configure(http);
    }
     
    /**
     * session过期
     * @return
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
 
 
}
四、项目文件结构截图

image.png

五、启动部署流程

1. 首先 在mysql创建数据库sso-oauth,在数据库执行sqlj脚本: sso-oauth-parent/sql/sso-oauth.sql

2. 然后用idea导入maven项目sso-oauth-parent, 待pom依赖jar包下载完成后执行下一步。

3. 配置redis

解压 so-oauth-parent\Redis-x64-3.0.504.zip 

双击 redis-server.exe   启动redis

image.png

image.png

4. 启动认证中心 , 启动client1, client2, client3 

image.png

其余client项目类似

4. 编译启动vue-demo( 如果没有前后端分离vue认证需求, 可以忽略本步。

要想启动vue项目,  首先安装nodejs环境 和vue环境,  比较简单参考百度

接下来进入:  sso-oauth-parent\vue-demo 目录, 执行cmd命令

npm install   # 安装vue依赖

npm install jquery   #安装jquery插件

npm run serve 启动服务

成功启动看到如下

image.png

六、演示效果

认证中心登录界面:

image.png

auth-center主页

image.png

无登录跳转client1主页

image.png

无登录跳转client2主页

image.png

无登录跳转client3-vue主页

image.png

退出登录

image.png

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很高兴为您解答这个问题。首先,让我们来看看如何使用缓存存储账号密码。 在 C# 中,可以使用 System.Web.Caching 命名空间中的 Cache 类来实现缓存。以下是一个简单示例: ```csharp using System.Web; using System.Web.Caching; public static class CacheHelper { public static void AddToCache(string key, object value, int minutesToCache) { HttpContext.Current.Cache.Insert(key, value, null, DateTime.Now.AddMinutes(minutesToCache), Cache.NoSlidingExpiration); } public static object GetFromCache(string key) { return HttpContext.Current.Cache[key]; } public static void RemoveFromCache(string key) { HttpContext.Current.Cache.Remove(key); } } ``` 在上面的代码中,AddToCache 方法用于将值存储到缓存中,GetFromCache 方法用于从缓存中检索值,RemoveFromCache 方法用于从缓存中删除值。minutesToCache 参数指定了缓存的时间,以分钟为单位。 现在,让我们来看看如何实现简单的注册和登录功能。以下是一个基本的示例: ```csharp using System; using System.Collections.Generic; public static class UserDatabase { private static Dictionary<string, string> users = new Dictionary<string, string>(); public static void RegisterUser(string username, string password) { if (!users.ContainsKey(username)) { users.Add(username, password); } else { throw new Exception("User already exists"); } } public static bool LoginUser(string username, string password) { if (users.ContainsKey(username) && users[username] == password) { CacheHelper.AddToCache(username, password, 10); return true; } else { return false; } } public static bool LogoutUser(string username) { CacheHelper.RemoveFromCache(username); return true; } public static bool IsLoggedIn(string username) { return CacheHelper.GetFromCache(username) != null; } } ``` 在上面的代码中,UserDatabase 类用于存储用户账号和密码。RegisterUser 方法用于注册用户,LoginUser 方法用于登录用户,LogoutUser 方法用于登出用户,IsLoggedIn 方法用于检查用户是否已经登录。当用户成功登录时,它会将用户的用户名和密码存储到缓存中,并在 10 分钟后过期。 现在,让我们来看看如何实现单点登录。Spring Cloud OAuth2 是一个开源项目,可以帮助您实现 OAuth2 协议。以下是一个简单示例: ```csharp using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace SSO.Controllers { [ApiController] [Route("[controller]")] public class UserController : ControllerBase { private readonly ILogger<UserController> _logger; public UserController(ILogger<UserController> logger) { _logger = logger; } [HttpGet] public IEnumerable<string> Get() { return new string[] { "Welcome to Single Sign-On" }; } [HttpPost] [Route("login")] public IActionResult Login(User user) { if (UserDatabase.LoginUser(user.Username, user.Password)) { return Ok(); } else { return Unauthorized(); } } [HttpPost] [Route("logout")] public IActionResult Logout(User user) { if (UserDatabase.LogoutUser(user.Username)) { return Ok(); } else { return NotFound(); } } [HttpGet] [Route("isloggedin")] public IActionResult IsLoggedIn(User user) { if (UserDatabase.IsLoggedIn(user.Username)) { return Ok(); } else { return Unauthorized(); } } } public class User { public string Username { get; set; } public string Password { get; set; } } } ``` 在上面的代码中,UserController 类用于处理用户的登录和登出请求,IsLoggedIn 方法用于检查用户是否已经登录。当用户登录时,它会将用户的用户名和密码存储到缓存中,并在 10 分钟后过期。 以上是一个简单示例,仅供参考。如果您想要实现更复杂的功能,可以参考更多的教程和文档。希望这个答案可以帮助到您,谢谢!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西安未央

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值