android系统定制开发2万字带你从0到1搭建一套企业级微服务安全框架

💥 💥

文章目录

基于上面Spring Securityandroid系统定制开发的几十个章节的学习,android系统定制开发想必大家对Spring Securityandroid系统定制开发已经有了一定的了解。

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

技术栈

  • 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;@Componentpublic interface UserMapper extends BaseMapper<User> {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
package com.ossa.system.service;import com.baomidou.mybatisplus.extension.service.IService;import com.ossa.common.api.bean.User;public interface UserService extends IService<User> {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
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;@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User>  implements UserService {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

认证设计

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

整体的设计思想:通过用户名和密码完成认证,确认用户可信,根据用户信息获取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<Object> 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<User>().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<String, Object> authInfo = new HashMap<String, Object>(2) {{            put("token", properties.getTokenStartWith() + token);            put("user", userDto);        }};        return ResponseEntity.ok(authInfo);    }
  • 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
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<User>().eq("username", username));        } catch (EntityNotFoundException e) {            // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException            throw new UsernameNotFoundException("", e);        }        if (user == null) {            throw new UsernameNotFoundException("");        } else {            List<Role> roles = roleMapper.listByUserId(user.getId());            ArrayList<Privilege> privileges = new ArrayList<>();            roles.forEach(role -> privileges.addAll(privilegeMapper.listByRoleId(role.getId())));            ArrayList<String> tag = new ArrayList<>();            privileges.forEach(p -> tag.add(p.getTag()));            List<SimpleGrantedAuthority> collect = tag.stream().map(SimpleGrantedAuthority::new)                    .collect(Collectors.toList());            userDetails = new org.springframework.security.core.userdetails.User(username, user.getPassword(), collect);        }        return userDetails;    }}
  • 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
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@Componentpublic 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

授权设计

  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
  • 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

核心配置

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<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();        // 获取匿名标记        Map<String, Set<String>> 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<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {        Map<String, Set<String>> anonymousUrls = new HashMap<>(6);        Set<String> get = new HashSet<>();        Set<String> post = new HashSet<>();        Set<String> put = new HashSet<>();        Set<String> patch = new HashSet<>();        Set<String> delete = new HashSet<>();        Set<String> all = new HashSet<>();        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {            HandlerMethod handlerMethod = infoEntry.getValue();            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);            if (null != anonymousAccess) {                List<RequestMethod> 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
  • 179

自定义权限注解

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<String> 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;@Componentpublic 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());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
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;@Componentpublic 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

网关处理

网关只需要转发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() *      * *  &#064;Bean *  public Contract feignContract(){ *      return new Contract.Default(); *  } * ==================================== */@Configurationpublic 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<String> headerNames = request.getHeaderNames();        if (headerNames != null) {            while (headerNames.hasMoreElements()) {                String name = headerNames.nextElement();                String values = request.getHeader(name);                requestTemplate.header(name, values);            }        }        Enumeration<String> 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

热门专栏 欢迎订阅

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发