2023-06-18
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81502532

重构用户名密码登录

202306181907390301.png

让自己的逻辑获取token的话,oath前面的逻辑都不能使用。使用我们自己的逻辑来代替。
也就是相当于只使用后面的功能;把自定义认证模式添加进来

在AuthenticationSuccessHandle中存在authentication对象,

所以只要获取到 ClientDetails和TokenRequest即可; 有时间了查看源码找这些吧

202306181907424252.png

思路

  1. 提交登录请求

  2. 登录成功之后,需要在上图AuthenticationSuccessHandler中获取相关信息

    • 前提条件:必须携带basic client信息(因为需要它获取clientDetails信息)
  3. 然后走后面的逻辑

处理登录后的逻辑

    package cn.mrcode.imooc.springsecurity.securitycore.authentication;
    
    import cn.mrcode.imooc.springsecurity.securitycore.properties.LoginType;
    import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
    import org.apache.commons.collections4.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.oauth2.common.OAuth2AccessToken;
    import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
    import org.springframework.security.oauth2.config.annotation.configuration.ClientDetailsServiceConfiguration;
    import org.springframework.security.oauth2.provider.*;
    import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
    import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
    import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Base64;
    
    /**
     * 复制之前写好的handler进行修改,只支持app这种模式
     * @author zhuqiang
     * @version 1.0.1 2018/8/3 16:29
     * @date 2018/8/3 16:29
     * @since 1.0
     */
    @Component("myAuthenticationSuccessHandler")
    public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
        private org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
    
        //  com.fasterxml.jackson.databind.
        // spring 是使用jackson来进行处理返回数据的
        // 所以这里可以得到他的实例
        @Autowired
        private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
    
        @Autowired
        private SecurityProperties securityProperties;
    
        /**
         * 授权服务器:自动配置的
         * @see ClientDetailsServiceConfiguration#clientDetailsService()
         */
        @Autowired
        private ClientDetailsService clientDetailsService;
    
        @Autowired
        private AuthorizationServerTokenServices authorizationServerTokenServices;
    
        /**
         * @param request
         * @param response
         * @param authentication 封装了所有的认证信息
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            logger.info("登录成功");
            /**
             * @see BasicAuthenticationFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
             *  */
            String header = request.getHeader("Authorization");
    
            if (header == null || !header.startsWith("Basic ")) {
                // 不被认可的客户端异常
                throw new UnapprovedClientAuthenticationException("没有Authorization请求头");
            }
    
            // 解析请Authorization 获取client信息
            // client-id: myid
            // client-secret: myid
            String[] tokens = extractAndDecodeHeader(header, request);
            assert tokens.length == 2;
            String clientId = tokens[0];
            String clientSecret = tokens[1];
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            // 判定提交的是否与查询的匹配
    
            if (clientDetails == null) {
                throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
            } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
                throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
            }
    
            /**  @see DefaultOAuth2RequestFactory#createTokenRequest(java.util.Map, org.springframework.security.oauth2.provider.ClientDetails)
             * requestParameters,不同的授权模式有不同的参数,这里自定义的模式,没有参数
             * String clientId,
             * Collection<String> scope, 给自己的前段使用,默认用所有的即可
             * String grantType 自定义
             *
             * 在这里我就有一个疑问了:这个token应该代表的是不同的用户,这里使用我们配置的同一个client?那么获取到的不就是相同的token?
             * 难道说是根据用户名和密码创建的?以后明白了再来填坑
             * */
    
            TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_SORTED_MAP, clientId, clientDetails.getScope(), "costom");
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
    
            /**
             * @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest)
             * */
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
    
            // 在后面测试的时候居然抛出了一个 事物异常 Could not open JDBC Connection for transaction; nested exception is ja
            // 我的数据库密码写错了,这个方法上加了一个@Transactional注解
            OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(accessToken));
        }
    
        /**
         * Decodes the header into a username and password.
         * @throws BadCredentialsException if the Basic header is not present or is not valid
         *                                 Base64
         */
        private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
    
            byte[] base64Token = header.substring(6).getBytes("UTF-8");
            byte[] decoded;
            try {
                decoded = Base64.getDecoder().decode(base64Token);
            } catch (IllegalArgumentException e) {
                throw new BadCredentialsException(
                        "Failed to decode basic authentication token");
            }
    
            String token = new String(decoded, "UTF-8");
    
            int delim = token.indexOf(":");
    
            if (delim == -1) {
                throw new BadCredentialsException("Invalid basic authentication token");
            }
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }

资源服务器安全配置

其实资源服务器的安全配置就类似普通服务的security的配置,对资源的保护;
spring oath2是建立的security的逻辑上的;不做用户认证,只做授权服务器,发送令牌,验证令牌等功能

所以按照之前配置过的copy过来快速修改,跑起来再细化修改

    package cn.mrcode.imooc.springsecurity.securityapp;
    
    /*
     *
     * ${desc}
     * @author zhuqiang
     * @version 1.0.1 2018/8/7 13:15
     * @date 2018/8/7 13:15
     * @since 1.0
     * */
    
    import cn.mrcode.imooc.springsecurity.securitycore.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
    import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityConstants;
    import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
    import cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCodeSecurityConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.social.security.SpringSocialConfigurer;
    
    @Configuration
    @EnableResourceServer
    public class MyResourcesServerConfig extends ResourceServerConfigurerAdapter {
        @Autowired
        private SecurityProperties securityProperties;
    
        // 由下面的  .apply(smsCodeAuthenticationSecurityConfigs)方法添加这个配置
        @Autowired
        private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfigs;
    
        @Autowired
        private ValidateCodeSecurityConfig validateCodeSecurityConfig;
        /**
         * @see SocialConfig#imoocSocialSecurityConfig()
         */
        @Autowired
        private SpringSocialConfigurer imoocSocialSecurityConfig;
    
        @Autowired
        private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
        @Autowired
        private AuthenticationFailureHandler myAuthenticationFailureHandler;
    
    
        // 有三个configure的方法,这里使用http参数的
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            // 之前配置的security的basic的 不能去掉哦。否则授权码模式又不能使用了
            http.formLogin()
                    .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                    .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                    .successHandler(myAuthenticationSuccessHandler)
                    .failureHandler(myAuthenticationFailureHandler)
            ;
            http
                    // 视频中说验证码的功能还有一点问题,先不用
    //                .apply(validateCodeSecurityConfig)
    //                .and()
                    .apply(smsCodeAuthenticationSecurityConfigs)
                    .and()
                    .apply(imoocSocialSecurityConfig)
                    .and()
                    // 对请求授权配置:注意方法名的含义,能联想到一些
                    .authorizeRequests()
                    // 放行这个路径
                    .antMatchers(
                            SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                            SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                            securityProperties.getBrowser().getLoginPage(),
                            SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*", // 图形验证码接口
                            securityProperties.getBrowser().getSignUpUrl(),  // 注册页面
                            "/user/regist",
                            "/error",
                            "/connect/*",
                            "/auth/*",
                            "/signin"
                    )
                    .permitAll()
                    .anyRequest()
                    // 对任意请求都必须是已认证才能访问
                    .authenticated()
                    .and()
                    .csrf()
                    .disable()
            ;
        }
    }

验证流程是否ok

Authorization 头还是写之前的myid的client信息;访问之前蒂尼的表单登录地址

    POST /authentication/form HTTP/1.1
    Host: localhost:8080
    Authorization: Basic bXlpZDpteWlk
    Content-Type: application/x-www-form-urlencoded
    Cache-Control: no-cache
    Postman-Token: 932d7563-34c1-002c-e2c1-8923757877c0
    
    username=admin&password=123456

成功获取到token信息

    {
        "access_token": "ab9bba42-f954-44d4-8e40-e4d6d81bfe60",
        "token_type": "bearer",
        "refresh_token": "e54ad634-127d-456b-9bf1-3b40c9d43017",
        "expires_in": 43199
    }
阅读全文