zoukankan      html  css  js  c++  java
  • spring-security【表单认证】、【jwt认证】、【社交登录】

    前言

    1.spring-security是spring官方推荐的【认证、授权】框架

    2.本文介绍spring-security在【表单认证】、【jwt认证】、【社交登录】3种场景中的运用

    3.RBAC权限模块的代码,将以伪代码形式给出

    总体介绍

    过滤器链

    spring-security使用多个Filter来实现:认证、授权、记住我、成功(失败)跳转、跨域访问、防跨站攻击。。。一系列功能。

    其中【UsernamePasswordAuthenticationFilter】与【BasicAuthenticationFilter】为核心过滤器(与业务绑定),分别管理【用户名/密码的认证】与【token认证】

    认证、授权流程

    第一步:认证
    根据username查询系统中是否存在该用户
    第二步:授权
    认证成功后,获取该用户的权限,封装为UserDetail对象(上图对于理解spring security整套认证、授权流程至关重要)
    PS:根据此图,调试代码,弄明白spring-security整个认证、授权流程,那么这套框架的核心就理解的差不多了

    案例

    表单认证

    1.流程图

    2.实现

    核心配置

        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            // 允许iframe嵌套,解决:frame because it set 'X-Frame-Options' to 'deny 问题
            http.headers().frameOptions().disable();
            // 放行所有option请求
            http.authorizeRequests()
                    .requestMatchers(CorsUtils::isPreFlightRequest)
                    .permitAll();
    
            http.csrf().disable()   // 关闭跨站攻击保护,否则无法进行跨域访问
                    .cors()         // 开启跨域支持
                    .and().formLogin()
                        .loginPage("/login.html")   // 登录页
                        .loginProcessingUrl("/user/login")   // 登录Action提交url
                        .defaultSuccessUrl("/success.html").permitAll()
                        .failureUrl("/error.html")
    //                    .failureHandler(failureHandler)     // 登录失败自定义处理器(会覆盖failureUrl()的设置)
    //                    .successHandler(successHandler)     // 登录成功自定义处理器(会覆盖defaultSuccessUrl()的设置)
                    .and().logout()   // 退出登录
                        .logoutUrl("/logout")   // 退出Action提交url
                        .addLogoutHandler(definedLogoutHandler) // 退出处理逻辑编写
                    .and().exceptionHandling().accessDeniedPage("/unauth.html") // 无权限访问时跳转的html
                    // 放开访问权限的url
                    .and().authorizeRequests()
                        .antMatchers("/", "/user/login", "/logout", "/user/logout").permitAll()
                        .anyRequest().authenticated();
    //                .and().rememberMe()   // 【记住我】功能设置
    //                .and().sessionManagement()    // session管理:session有效期、session个数(可实现互踢)
    
        }
    View Code

    配置的说明,注释已经说的很详细了,可自行阅读
    说明:

    a. 跨站攻击【.csrf().disable()】必须要关闭,否则跨域配置【.cors()】不起作用
    b. 登录action的url【/user/login】与退出登录action的url【/logout】只需要配置路径即可,不需要自己实现,由spring-security框架自行实现

     跨域配置

        @Bean
        CorsFilter corsFilter() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.addAllowedOrigin(CorsConfiguration.ALL);
            configuration.addAllowedHeader(CorsConfiguration.ALL);
            configuration.addAllowedMethod(CorsConfiguration.ALL);
            configuration.setAllowCredentials(true);
    
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return new CorsFilter(source);
        }
    View Code

    补充

    FormLogin模式虽然依托于session,但是仍然可以实现微服务架构下的单点登录

    实现步骤:

    a. session需要保存在一个公共可访问区域(数据库 or redis)
    b. session登录后,需要扩展session的作用域范围

    例如:两个服务:www.baidu.com,map.baidu.com

    当在www.baidu.com登录成功后,得到的session的作用域为www.baidu.com,此时我们需要修改session的作用域为:baidu.com。
    这样,当我们在登录成功的状态下,再访问map.baidu.com时,由于session的作用域为baidu.com,所以session在map.baidu.com下仍然有效,所以登录状态为【已登录】

    jwt认证

    1.流程图

    BasicLogin模式,采用的是http身份认证,属于最简单的认证、授权功能

    2.实现

    jwt的认证过程,有两步:【登录-生成token】、【验证token】需要将以上两个步骤,封装为Filter,添加到spring-security整个Filter链中

    1.登录-生成token

    a. 自定义Filter,用此Filter替换掉UsernamePasswordAuthenticationFilter,这样过滤器链的【用户名-密码认证】,用的就是我们自定义的业务逻辑
    b. 继承UsernamePasswordAuthenticationFilter,重写【获取用户传递的信息方法(attemptAuthentication)】、【认证成功方法(successfulAuthentication)】、【认证失败方法(unsuccessfulAuthentication)】,实现自定义认证逻辑

    核心代码

    获取表单数据:spring-security的认证方法,需要将用户输入封装为:UsernamePasswordAuthenticationToken(用户名,密码,权限)

        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException {
            //获取表单提交数据
            try {
                User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
                return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),
                        user.getPassword()));
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException();
            }
        }
    View Code

    认证成功

        @Override
        protected void successfulAuthentication(HttpServletRequest request,
                                                HttpServletResponse response, FilterChain chain, Authentication authResult) {
            //认证成功,得到认证成功之后用户信息
            SecurityUser user = (SecurityUser) authResult.getPrincipal();
            //根据用户名生成token
            String token = tokenManager.generateToken(user);
            //返回token
            ResponseUtil.out(response, token);
        }
    View Code

    认证失败

        @Override
        protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                                  AuthenticationException failed) {
            ResponseUtil.out(response, "认证失败");
        }
    View Code

    2.验证token

    a. 自定义Filter,用此Filter替换掉BasicAuthenticationFilter。由于jwt依托于http协议,BasicAuthenticationFilter就是针对http的Filter,所以选择替换掉BasicAUthenticationFilter
    b. 继承BasicAuthenticationFilter,重写【过滤器方法(doFilterInternal)】实现自定义业务逻辑

    核心代码

    认证过滤

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            //获取当前认证成功用户权限信息
            UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
            //判断如果有权限信息,放到权限上下文中
            if (authRequest != null) {
                SecurityContextHolder.getContext().setAuthentication(authRequest);
            }
            chain.doFilter(request, response);
        }
    View Code

    获取用户认证信息

        private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
            //从header获取token
            String token = request.getHeader("token");
            if (token != null) {
                // 用户名
                String username = tokenManager.getUsernameFromToken(token);
    
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    if (tokenManager.validateToken(token, userDetails)) {
                        //给使用该JWT令牌的用户进行授权
                        UsernamePasswordAuthenticationToken authenticationToken
                                = new UsernamePasswordAuthenticationToken(userDetails, token,
                                userDetails.getAuthorities());
                        return authenticationToken;
                    }
                }
            }
            return null;
        }
    View Code

    社交登录

    1.流程图

    a. 引导【client】跳转至【第三方应用-登录】进行【登录-授权】操作,成功后返回【code】
    b. 【client】获取到【code】向【第三方应用-认证】换取【access_token】
    c. 【client】获取到【access_token】向【第三方应用-资源】发起API请求,获取开放的【用户信息】

    2.实现思路

    a. 【登录功能】由【第三方平台】实现,登录成功后,需要将返回的信息保存入本地。与【本地用户】建立关联关系
    b. 【认证功能】与jwt认证功能类似,只需要做微调

    3.实现(本实例使用【新浪微博开放平台】)

    1.平台地址:https://open.weibo.com/

    2.应用信息

     

    3.回调信息

     

    4.操作步骤

     

    5.access_token

    【uid】用户标志id,需要用它与本地用户建立关联关系
    【expires_in】access_token有效日期,生成jwt时有效期需要使用该属性

    6.生成token

    a. 在【第三方平台】的回调url中,获取到返回的code
    b. 使用code,向【第三方平台】换取对应的access_token
    c. access_token中的uid与本地用户User,建立对应关系

    @RestController
    @RequestMapping("/oauth")
    public class OAuthController {
    
        @Autowired
        TokenManager tokenManager;
    
        @Autowired
        MyUserDetailsService myUserDetailsService;
    
        // 应该存在redis中
        public static Map<String, Social> socials = new HashMap<>();
    
        private static final String KEY = "YOU APP ID";
        private static final String SECRET = "YOU SECURITY";
    
        @GetMapping("/token")
        public String token(@RequestParam String code) {
    
            // 获取access_token
            String url = "https://api.weibo.com/oauth2/access_token";
            UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url)
                    // Add query parameter
                    .queryParam("client_id", KEY)
                    .queryParam("client_secret", SECRET)
                    .queryParam("grant_type", "authorization_code")
                    .queryParam("redirect_uri", "http://192.168.0.14:9730/oauth/token")
                    .queryParam("code", code);
    
            RestTemplate restTemplate = new RestTemplateBuilder().build();
            Social social = restTemplate.postForObject(builder.toUriString(), null, Social.class);
            socials.put(social.getUid(), social);
    
            // 生成token
            User user = new User("admin", "123", "社交登录id");
            SecurityUser securityUser = new SecurityUser(user, social, null);
            String token = tokenManager.generateToken(securityUser);
            return token;
        }
    }
    View Code

    7.剩余部分

    剩余部分与jwt大同小异,此处不再赘述,可自行查看源码

    结束语:感谢大家的耐心阅读 

    代码下载

    示例代码下载

  • 相关阅读:
    2018-12-25-dot-net-double-数组转-float-数组
    2018-12-25-dot-net-double-数组转-float-数组
    2019-10-24-dotnet-列表-Linq-的-Take-用法
    2019-10-24-dotnet-列表-Linq-的-Take-用法
    2018-8-10-C#-代码占用的空间
    2018-8-10-C#-代码占用的空间
    2018-4-29-C#-金额转中文大写
    2018-4-29-C#-金额转中文大写
    Java实现 LeetCode 630 课程表 III(大小堆)
    Java实现洛谷 P1072 Hankson 的趣味题
  • 原文地址:https://www.cnblogs.com/color-wolf/p/14760796.html
Copyright © 2011-2022 走看看