• 2万字带你从0到1搭建一套企业级微服务安全框架


    ??《微服务核心技术》专栏已收录,欢迎订阅 ??

    文章目录

    基于上面Spring Security的几十个章节的学习,想必大家对Spring Security框架已经有了一定的了解。

    那么我们开始从零开始搭建一套微服务的安全框架,希望其中的一些思想能给大家一些启发。

    技术栈

    • spiring security
    • jwt
    • redis
    • nacos registry
    • spring cloud gateway
    • sentinel
    • nacos config
    • seata
    • mybatis
    • mybatis-plus
    • xxl-job
    • rocketmq

    数据交互与实现

    说到安全就会涉及认证和授权,那么对什么认证,对什么授权,于是引出如下几张表。

    • 用户表
    • 角色表
    • 权限表

    这也是典型的RBAC模型。

    所有数据表以及项目源码可以搜公号【】回复「1024」即可获得。

    在这里插入图片描述
    有了数据表,我们来完善具体的代码实现。

    数据交互的实现

    在这里插入图片描述

    部分代码:

    package com.ossa.system.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.ossa.common.api.bean.User;
    import org.springframework.stereotype.Component;
    
    @Component
    public interface UserMapper extends BaseMapper {
    }
    
    
    
    package com.ossa.system.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.ossa.common.api.bean.User;
    
    public interface UserService extends IService {
    }
    
    
    
    package com.ossa.system.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.ossa.common.api.bean.User;
    import com.ossa.system.mapper.UserMapper;
    import com.ossa.system.service.UserService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl extends ServiceImpl  implements UserService {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    认证设计

    通过登录操作完成认证,首先在配置类中应该放过登录的请求,我在这里实现一个匿名注解,会在后面给出代码和解析。

    整体的设计思想:通过用户名和密码完成认证,确认用户可信,根据用户信息获取token,每次请求都带上token,完成校验。

    1. 获取传参的用户信息,用户名、密码等。String password = authUser.getPassword();
    2. 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
    3. 获取认证管理器AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
    4. 认证Authentication authentication = authenticationManager.authenticate(authenticationToken);
    5. 重写UserDetailsService,从数据库获取用户信息,以完成认证流程。
    6. 认证成功后,根据认证信息生成token
    7. 可将token作为key存入redis,用redis的过期时间代替jwt的token令牌的过期时间
    8. 获取用户身份信息
    9. 将token信息及用户信息返回。

    代码实现:

        @PostMapping("/login")
        @AnonymousAccess
        public ResponseEntity login(@Validated @RequestBody AuthUserDto authUser){
            // 密码解密
    //        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
            String password = authUser.getPassword();
            // 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
            // 获取认证管理器
            AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
            // 认证核心方法
            Authentication authentication = authenticationManager.authenticate(authenticationToken);
    //        // 认证成功之后,将认证信息保存至SecurityContext中
    //        SecurityContextHolder.getContext().setAuthentication(authentication);
            // 根据认证信息生成token
            String token = tokenProvider.createToken(authentication);
            // 获取用户身份信息
            User one = userService.getOne(new QueryWrapper().eq("username", authUser.getUsername()));
            UserDto userDto = new UserDto();
            BeanUtils.copyProperties(one,userDto);
    
            stringRedisTemplate.opsForValue().set(properties.getOnlineKey() + token, JSONUtil.toJsonStr(userDto), properties.getTokenValidityInSeconds()/1000, TimeUnit.SECONDS);
    
            // 返回 token 与 用户信息
            Map authInfo = new HashMap(2) {{
                put("token", properties.getTokenStartWith() + token);
                put("user", userDto);
            }};
            return ResponseEntity.ok(authInfo);
        }
    
    
    package com.ossa.system.filter;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.ossa.common.api.bean.Privilege;
    import com.ossa.common.api.bean.Role;
    import com.ossa.common.api.bean.User;
    import com.ossa.system.mapper.PrivilegeMapper;
    import com.ossa.system.mapper.RoleMapper;
    import com.ossa.system.service.UserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import javax.persistence.EntityNotFoundException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @RequiredArgsConstructor
    @Service("userDetailsService")
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        private final UserService userService;
        private final RoleMapper roleMapper;
        private final PrivilegeMapper privilegeMapper ;
    
    
        @Override
        public UserDetails loadUserByUsername(String username) {
            User user;
            org.springframework.security.core.userdetails.User userDetails;
            try {
                user = userService.getOne(new QueryWrapper().eq("username", username));
    
            } catch (EntityNotFoundException e) {
                // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
                throw new UsernameNotFoundException("", e);
            }
            if (user == null) {
                throw new UsernameNotFoundException("");
            } else {
    
                List roles = roleMapper.listByUserId(user.getId());
    
                ArrayList privileges = new ArrayList<>();
    
                roles.forEach(role -> privileges.addAll(privilegeMapper.listByRoleId(role.getId())));
    
                ArrayList tag = new ArrayList<>();
    
                privileges.forEach(p -> tag.add(p.getTag()));
    
                List collect = tag.stream().map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());
                userDetails = new org.springframework.security.core.userdetails.User(username, user.getPassword(), collect);
    
            }
            return userDetails;
        }
    }
    
    
    package com.ossa.system.filter;
    
    
    import cn.hutool.core.date.DateField;
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.IdUtil;
    import com.ossa.common.bean.SecurityProperties;
    import io.jsonwebtoken.*;
    import io.jsonwebtoken.io.Decoders;
    import io.jsonwebtoken.security.Keys;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.security.Key;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    @Component
    public class TokenProvider implements InitializingBean {
    
        private final SecurityProperties properties;
        private final StringRedisTemplate stringRedisTemplate;
        public static final String AUTHORITIES_KEY = "user";
        private JwtParser jwtParser;
        private JwtBuilder jwtBuilder;
    
        public TokenProvider(SecurityProperties properties, StringRedisTemplate stringRedisTemplate) {
            this.properties = properties;
            this.stringRedisTemplate = stringRedisTemplate;
        }
    
        @Override
        public void afterPropertiesSet() {
            byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
            Key key = Keys.hmacShaKeyFor(keyBytes);
            jwtParser = Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build();
            jwtBuilder = Jwts.builder()
                    .signWith(key, SignatureAlgorithm.HS512);
        }
    
        /**
         * 创建Token 设置永不过期,
         * Token 的时间有效性转到Redis 维护
         *
         * @param authentication /
         * @return /
         */
        public String createToken(Authentication authentication) {
            return jwtBuilder
                    // 加入ID确保生成的 Token 都不一致
                    .setId(IdUtil.simpleUUID())
                    .claim(AUTHORITIES_KEY, authentication.getName())
                    .setSubject(authentication.getName())
                    .compact();
        }
    
        /**
         * 依据Token 获取鉴权信息
         *
         * @param token /
         * @return /
         */
        Authentication getAuthentication(String token) {
            Claims claims = getClaims(token);
            User principal = new User(claims.getSubject(), "******", new ArrayList<>());
            return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
        }
    
        public Claims getClaims(String token) {
            return jwtParser
                    .parseClaimsJws(token)
                    .getBody();
        }
    
        /**
         * @param token 需要检查的token
         */
        public void checkRenewal(String token) {
            // 判断是否续期token,计算token的过期时间
            Long expire = stringRedisTemplate.getExpire(properties.getOnlineKey() + token, TimeUnit.SECONDS);
            long time = expire == null ? 0 : expire * 1000;
            Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
            // 判断当前时间与过期时间的时间差
            long differ = expireDate.getTime() - System.currentTimeMillis();
            // 如果在续期检查的范围内,则续期
            if (differ <= properties.getDetect()) {
                long renew = time + properties.getRenew();
                stringRedisTemplate.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
            }
        }
    
        public String getToken(HttpServletRequest request) {
            final String requestHeader = request.getHeader(properties.getHeader());
            if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
                return requestHeader.substring(7);
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207

    授权设计

    1. 设计自己filter,拦截我们生成的token,如果token合法,则将token解析并封装成UsernamePasswordAuthenticationToken,存到安全上下文中

    2. 为了确保授权成功,我们需要将我们的filter放在UsernamePasswordAuthenticationFilter前执行

      package com.ossa.system.filter;

      import cn.hutool.core.util.StrUtil;
      import com.ossa.common.bean.SecurityProperties;
      import io.jsonwebtoken.ExpiredJwtException;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.security.core.Authentication;
      import org.springframework.security.core.context.SecurityContextHolder;
      import org.springframework.util.StringUtils;
      import org.springframework.web.filter.GenericFilterBean;

      import javax.servlet.FilterChain;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServletRequest;
      import java.io.IOException;

      public class OssaTokenFilter extends GenericFilterBean {
      private static final Logger log = LoggerFactory.getLogger(OssaTokenFilter.class);

      private final StringRedisTemplate stringRedisTemplate;
      
      private final TokenProvider tokenProvider;
      private final SecurityProperties properties;
      
      /**
       * @param tokenProvider     Token
       * @param properties        JWT
       */
      public OssaTokenFilter(TokenProvider tokenProvider, SecurityProperties properties, StringRedisTemplate stringRedisTemplate) {
          this.properties = properties;
          this.tokenProvider = tokenProvider;
          this.stringRedisTemplate = stringRedisTemplate;
      }
      
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
              throws IOException, ServletException {
          HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
          String token = resolveToken(httpServletRequest);
          // 对于 Token 为空的不需要去查 Redis
          if (StrUtil.isNotBlank(token)) {
              String s = null;
              try {
                  s = stringRedisTemplate.opsForValue().get(properties.getOnlineKey() + token);
              } catch (ExpiredJwtException e) {
                  log.error(e.getMessage());
              }
              if (s != null && StringUtils.hasText(token)) {
                  Authentication authentication = tokenProvider.getAuthentication(token);
                  SecurityContextHolder.getContext().setAuthentication(authentication);
                  // Token 续期
                  tokenProvider.checkRenewal(token);
              }
          }
          filterChain.doFilter(servletRequest, servletResponse);
      }
      
      /**
       * 初步检测Token
       *
       * @param request /
       * @return /
       */
      private String resolveToken(HttpServletRequest request) {
          String bearerToken = request.getHeader(properties.getHeader());
          if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
              // 去掉令牌前缀
              return bearerToken.replace(properties.getTokenStartWith(), "");
          } else {
              log.debug("非法Token:{}", bearerToken);
          }
          return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54

      }

    核心配置

    package com.ossa.common.security.core.config;
    
    import com.ossa.common.api.anno.AnonymousAccess;
    import com.ossa.common.api.bean.SecurityProperties;
    import com.ossa.common.api.enums.RequestMethodEnum;
    import com.ossa.common.security.core.filter.OssaTokenFilter;
    import com.ossa.common.security.core.filter.TokenProvider;
    import com.ossa.common.security.core.handler.JwtAccessDeniedHandler;
    import com.ossa.common.security.core.handler.JwtAuthenticationEntryPoint;
    import lombok.RequiredArgsConstructor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.core.GrantedAuthorityDefaults;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
    
    import java.util.*;
    
    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class OssaSecurityConfigurer extends WebSecurityConfigurerAdapter {
        private final TokenProvider tokenProvider;
        private final SecurityProperties properties;
        private final ApplicationContext applicationContext;
        private final JwtAuthenticationEntryPoint authenticationErrorHandler;
    
        private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
    
        private final StringRedisTemplate stringRedisTemplate;
    
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
        GrantedAuthorityDefaults grantedAuthorityDefaults() {
            // 去除 ROLE_ 前缀
            return new GrantedAuthorityDefaults("");
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            // 密码加密方式
            return new BCryptPasswordEncoder();
    
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
    
            OssaTokenFilter customFilter = new OssaTokenFilter(tokenProvider, properties,stringRedisTemplate);
    
            // 搜寻匿名标记 url: @AnonymousAccess
            RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
            Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
            // 获取匿名标记
            Map> anonymousUrls = getAnonymousUrl(handlerMethodMap);
            httpSecurity
                    // 禁用 CSRF
                    .csrf().disable()
                    .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
    
                    // 授权异常
                    .exceptionHandling()
                    .authenticationEntryPoint(authenticationErrorHandler)
                    .accessDeniedHandler(jwtAccessDeniedHandler)
                    // 防止iframe 造成跨域
                    .and()
                    .headers()
                    .frameOptions()
                    .disable()
                    // 不创建会话
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 静态资源等等
                    .antMatchers(
                            HttpMethod.GET,
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/webSocket/**"
                    ).permitAll()
                    // swagger 文档
                    .antMatchers("/swagger-ui.html").permitAll()
                    .antMatchers("/swagger-resources/**").permitAll()
                    .antMatchers("/webjars/**").permitAll()
                    .antMatchers("/*/api-docs").permitAll()
                    // 文件
                    .antMatchers("/avatar/**").permitAll()
                    .antMatchers("/file/**").permitAll()
                    // 阿里巴巴 druid
                    .antMatchers("/druid/**").permitAll()
                    // 放行OPTIONS请求
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
                    // GET
                    .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
                    // POST
                    .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
                    // PUT
                    .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
                    // PATCH
                    .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
                    // DELETE
                    .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
                    // 所有类型的接口都放行
                    .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
    
                    // 所有请求都需要认证
                    .anyRequest().authenticated();
        }
    
        private Map> getAnonymousUrl(Map handlerMethodMap) {
            Map> anonymousUrls = new HashMap<>(6);
            Set get = new HashSet<>();
            Set post = new HashSet<>();
            Set put = new HashSet<>();
            Set patch = new HashSet<>();
            Set delete = new HashSet<>();
            Set all = new HashSet<>();
            for (Map.Entry infoEntry : handlerMethodMap.entrySet()) {
                HandlerMethod handlerMethod = infoEntry.getValue();
                AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
                if (null != anonymousAccess) {
                    List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
                    RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
                    switch (Objects.requireNonNull(request)) {
                        case GET:
                            get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                        case POST:
                            post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                        case PUT:
                            put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                        case PATCH:
                            patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                        case DELETE:
                            delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                        default:
                            all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                            break;
                    }
                }
            }
            anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
            anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
            anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
            anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
            anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
            anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
            return anonymousUrls;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    自定义权限注解

    package com.ossa.common.security.core.config;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Service;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Service(value = "pc")
    public class PermissionConfig {
    
        public Boolean check(String... permissions) {
            // 获取当前用户的所有权限
            List permission = SecurityContextHolder.getContext()
                    .getAuthentication()
                    .getAuthorities()
                    .stream()
                    .map(GrantedAuthority::getAuthority)
                    .collect(Collectors
                            .toList());
            // 判断当前用户的所有权限是否包含接口上定义的权限
            return permission.contains("ADMIN") || permission.contains("INNER") || permission.contains("OFFICEIT") || Arrays.stream(permissions).anyMatch(permission::contains);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    权限异常处理

    package com.ossa.common.security.core.handler;
    
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @Component
    public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
            //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
            response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
        }
    }
    
    
    
    
    package com.ossa.common.security.core.handler;
    
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @Component
    public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
        @Override
        public void commence(HttpServletRequest request,
                             HttpServletResponse response,
                             AuthenticationException authException) throws IOException {
            // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    网关处理

    网关只需要转发token到具体服务即可

    在写这篇文章之前,此部分我已经升级成UAA认证授权中心,故没有此处相关代码。

    内部流量处理

    在内部流量的设计过程中,我们并不需要网关分发的token,故在此设计时,我只在feign的api接口处统一增加权限标识,并经过简单加密。

    并在上述的自定的权限注解处放过该标识,不进行权限校验。

    package com.ossa.feign.config;
    
    import com.ossa.feign.util.EncryptUtil;
    import feign.Logger;
    import feign.Request;
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Enumeration;
    import java.util.Objects;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author issavior
     *
     * =================================
     *     **
     *      * 修改契约配置,支持Feign原生的注解
     *      * @return 返回 new Contract.Default()
     *      *
     *  @Bean
     *  public Contract feignContract(){
     *      return new Contract.Default();
     *  }
     * ====================================
     */
    @Configuration
    public class FeignClientConfig implements RequestInterceptor {
    
        /**
         * 超时时间配置
         *
         * @return Request.Options
         */
        @Bean
        public Request.Options options() {
    
            return new Request.Options(5, TimeUnit.SECONDS,
                    5, TimeUnit.SECONDS, true);
        }
    
        /**
         * feign的日志级别
         *
         * @return 日志级别
         */
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
        /**
         * 重写请求拦截器apply方法,循环请求头
         *
         * @param requestTemplate 请求模版
         */
        @Override
        public void apply(RequestTemplate requestTemplate) {
    
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (Objects.isNull(requestAttributes)) {
                return;
            }
            HttpServletRequest request = ((ServletRequestAttributes) (requestAttributes)).getRequest();
            Enumeration headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    requestTemplate.header(name, values);
                }
            }
            Enumeration bodyNames = request.getParameterNames();
    //        body.append("token").append("=").append(EncryptUtil.encodeUTF8StringBase64("INNER")).append("&");
            if (bodyNames != null) {
                while (bodyNames.hasMoreElements()) {
                    String name = bodyNames.nextElement();
                    String values = request.getParameter(name);
                    requestTemplate.header(name,values);
                }
            }
    
            requestTemplate.header("inner",EncryptUtil.encodeUTF8StringBase64("INNER"));
        }
    
    //    /**
    //     * 修改契约配置,支持Feign原生的注解
    //     * @return 返回 new Contract.Default()
    //     */
    //    @Bean
    //    public Contract feignContract(){
    //        return new Contract.Default();
    //    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    热门专栏 欢迎订阅

    1. 《Java系核心技术》
    2. 《中间件核心技术》
    3. 《微服务核心技术》
    4. 《云原生核心技术》
  • 相关阅读:
    全网最牛自动化测试框架系列之pytest(7)-yield与终结函数
    开放式运动耳机排行榜,排行靠前的五款高性能耳机分享
    python的/ 和// 学习
    在springBoot中同时使用mysql和MongoDB
    PET-MRI医学图像融合与混合神经胶质瘤分类模型
    宝玉:Sora 如何改变我们的生活
    分布式文件系统JuiceFS测试总结
    java算法第21天 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先
    合并报表软件选哪个?这篇文章两分钟告诉你!
    nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全
  • 原文地址:https://blog.csdn.net/hjseo_seg/article/details/126029624