💥 💥
文章目录
基于上面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,完成校验。
- 获取传参的用户信息,用户名、密码等。
String password = authUser.getPassword();
- 将用户名、密码、封装成UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
- 获取认证管理器
AuthenticationManager authenticationManager = authenticationManagerBuilder.getObject();
- 认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
- 重写
UserDetailsService
,从数据库获取用户信息,以完成认证流程。 - 认证成功后,根据认证信息生成token
- 可将token作为key存入redis,用redis的过期时间代替jwt的token令牌的过期时间
- 获取用户身份信息
- 将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
授权设计
- 设计自己filter,拦截我们生成的token,如果token合法,则将token解析并封装成
UsernamePasswordAuthenticationToken
,存到安全上下文中 - 为了确保授权成功,我们需要将我们的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() * * * @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
热门专栏 欢迎订阅