zoukankan      html  css  js  c++  java
  • Spring OAuth2 GitHub 自定义登录信息

    # 相关代码

    https://github.com/mofadeyunduo/money

    0.1.3-SNAPSHOT

    security 模块中

    # 原因

    最近在做一款管理金钱的网站进行自娱自乐,发现没有安全控制岂不是大家都知道我的工资了(一脸黑线)?

    最近公司也在搞 Spring OAuth2,当时我没有时间(其实那时候不想搞)就没做,现在回头来学习学习。

    Spring OAuth2 官方的教程写的比较少,实用性比较差。

    # 教程内容

    1. Spring OAuth2 Github SSO
    2. 替换认证过的 Spring Security Authentication 对象
    3. 后端设置 Cookie 实现 Remember-Me 功能

    # Github 登录

    Spring OAuth2 Github,官网写的已经比较详细了。

    要注意的是必须要配置两个 Filter:

    • OAuth2ClientAuthenticationProcessingFilter ,用于处理 OAuth2 流程。
    • OAuth2ClientContextFilter,用于触发跳转到 OAuth2 请求。

    P.S. 这很诡异,我不知道 Spring OAuth2 为什么要这么做。

    # 替换认证过的 Spring Security Authentication

    步骤:

    1. 重写方法 getPrincipal,把 Authentication 中的 Principal 换成用户信息对象。
    2. 最好设置 AuthenticationSuccessHandler,默认的 AuthenticationSuccessHandler 会跳转到主页,并将 Cookie 清空,影响 RememberMeServices 认证。
        @Override
        protected Object getPrincipal(Map<String, Object> map) {
            String principal = String.class.cast(map.get("name"));
            // 从数据库读取用户
            UserWithAccount userWithAccount = userAndAccountService.getByPrincipalAndType(principal, AccountType.GITHUB);
            // 未获取到用户信息,保存
            if (Objects.isNull(userWithAccount)) {
                Account newAccount = new Account(null, AccountType.GITHUB, principal, new Gson().toJson(map));
                userWithAccount = userAndAccountService.accountSignup(newAccount);
            }
            // 替换原来的 OAuth2Authentication
            return userWithAccount;
        }
            githubFilter.setAuthenticationSuccessHandler(new GithubAuthenticationSuccessHandler());

    # 设置 Cookie

    1. 实现 UserDetailServices。
    2. 用 UserDetailServices 构造 RememberMeServices。这里采用的 RememberMeServices 的具体实现是 TokenBasedRememberMeServices。TokenBasedRememberMeServices 会用 UserDetailServices 构造 Authentication 的 Principal 对象。还有一个构造参数(key)是指定盐,指定一个值,该值在应用反复重启的时要保持不变。顺便提一下,TokenBasedRememberMeServices 加密是根据 UserDetailServices 构造出的 UserDetails 中的 username、password、时间戳、构造参数 key 进行 MD5 和 Base64 加密和解密。
    3. 在 OAuth2ClientAuthenticationProcessingFilter 和继承类 WebSecurityConfigurerAdapter 的方法 configure 注册 RememberMeServices。注册到 OAuth2ClientAuthenticationProcessingFilter 是为了在 OAuth2 认证成功或者失败之后设置 Cookie。在 configure 注册 RememberMeServices 是为了利用 Cookie 自动登录。
    public class CachingUserDetailsService implements UserDetailsService {
    
        private static final String USER_KEY = "user:%s";
    
        private StringRedisTemplate stringRedisTemplate;
        private UserAndAccountService userAndAccountService;
    
        public CachingUserDetailsService(StringRedisTemplate stringRedisTemplate, UserAndAccountService userAndAccountService) {
            this.stringRedisTemplate = stringRedisTemplate;
            this.userAndAccountService = userAndAccountService;
        }
    
        public UserDetails loadUserByUsername(String username) {
            String actualKey = String.format(USER_KEY, username);
            UserWithAccount userWithAccount;
            String userWithAccountJson = stringRedisTemplate.opsForValue().get(actualKey);
            if (StringUtils.isEmpty(userWithAccountJson)) {
                userWithAccount = userAndAccountService.getByUserId(Integer.parseInt(username));
                stringRedisTemplate.opsForValue().set(actualKey, new Gson().toJson(userWithAccount));
            } else {
                userWithAccount = new Gson().fromJson(userWithAccountJson, UserWithAccount.class);
            }
            return userWithAccount;
        }
    
    }
        @Bean
        public RememberMeServices rememberMeServices() {
            TokenBasedRememberMeServices tokenBasedRememberMeServices = new TokenBasedRememberMeServices(REMEMBER_ME_KEY, userDetailsService());
            tokenBasedRememberMeServices.setAlwaysRemember(true);
            tokenBasedRememberMeServices.setTokenValiditySeconds(ONE_DAY_IN_SECONDS);
            return tokenBasedRememberMeServices;
        }
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .rememberMe()
                    .key(REMEMBER_ME_KEY)
                    .rememberMeServices(rememberMeServices());
            http
                    .addFilterBefore(oAuth2ClientAuthenticationProcessingFilter(), BasicAuthenticationFilter.class); // Github OAuth2 登录的 Filter
        }
    
        @Bean
        public OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter() {
            OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/security/oauth2/github");
            OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(githubClient(), oauth2ClientContext);
            githubFilter.setAllowSessionCreation(false);
            githubFilter.setRestTemplate(facebookTemplate);
            githubFilter.setTokenServices(new GithubUserInfoTokenServices(githubResource().getUserInfoUri(), githubClient().getClientId(), userAndAccountService));
            githubFilter.setRememberMeServices(rememberMeServices());
            githubFilter.setAuthenticationSuccessHandler(new GithubAuthenticationSuccessHandler());
            return githubFilter;
        }

    # 后记

    我本来预估一周时间就把我项目的安全控制模块给开发完,结果被 Spring Oauth2 拖了一周。由于对 Spring 核心概念也不是特别熟悉,看代码也比较费事(虽然大部分看得懂)。最近一段时间准备写一套 IoC 方面的代码。

    我现在所在的公司之前有个人挺厉害,自己写了一套 IoC 容器用到了系统中。不过他也很坑,他写的 IoC 容器出了问题,根本找不到任何解决资料,只能看源码。自己写的只能做玩具玩玩吧。

  • 相关阅读:
    2017.4.6下午
    2017.4.6上午
    2017.3.31下午
    2017.4.5下午
    2017.4.5上午
    2017.4.1上午
    2017.3.31上午
    2017.3.28下午
    2017.3.28上午
    3.28上午
  • 原文地址:https://www.cnblogs.com/Piers/p/8563735.html
Copyright © 2011-2022 走看看