spring boot整合security记录
目录
因项目需要,需要整 一个Spring boot单应用,需要提前先引入Spring security做个简单的权限认证。用户信息存储在redis中,不与db做交互。下面主要写security的主要代码的引用,仅供自己参考
项目引入的maven依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--<version>2.1.4.RELEASE</version>-->
</dependency>
WebSecurityConfigurerAdapter 过滤器,做主要的权限认证
后面可以自己写安全策略,这里可以自己百度
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//放行/oauth/oauth/token的请求
.antMatchers("/oauth/oauth/token").permitAll()
// 所有请求都需要验证
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint())
.accessDeniedHandler(new MyAccessDeniedHandler())
.and()
.formLogin()
.and()
.csrf().disable();
}
}
从写token过滤器GenericFilterBean
这个主要用来控制哪些api不需要token认证,哪些需要鉴权,同时set新的用户上下文信息
重写doFilter方法,从request中获取requestUri,可以过滤不需要token认证的API。需要token认证的,我们通过解析token,拿到用户名,从redis中读取用户的相关信息,重新放到UsernamePasswordAuthenticationToken类中。
主要代码如下
@Slf4j
@Component
public class TokenAuthFilter extends GenericFilterBean {
@Autowired
private UserService userService;
@Resource
private RedisTemplate<String,Object> redisTemplate;
private static final String PROXY_TOKEN = "proxy:auth:token:";
public static final List<String> PUBLIC_URI = new ArrayList<String>(){{
add("/oauth/oauth/token");
}};
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestUri = httpServletRequest.getRequestURI();
if (!PUBLIC_URI.contains(requestUri)) {
String token = TokenUtil.getToken(httpServletRequest);
if (StringUtils.isNotEmpty(token)) {
verifyToken(token);
String username = new String(Base64.getDecoder().decode(token.getBytes(StandardCharsets.UTF_8)));
UserDetails userDetails = this.userService.loadUserByUsername(username.split("_")[0]);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new AccessDeniedException("no access permission");
}
}
filterChain.doFilter(servletRequest, response);
}
public void verifyToken(String token) {
// 从缓存中获取用户信息
Object userDetails = (Object)redisTemplate.opsForValue().get(PROXY_TOKEN + token);
if (null == userDetails) {
throw new AccessDeniedException("没有登录信息,请重新登录");
}
}
}
redis中保存UserDetails信息,在我们生成token时,set进去
主要登录信息,登录是不需要token鉴权的,所以在TokenAuthFilter过滤器中,我们需要剔除这个API,包括后续还有不需要token鉴权的,我们可以同样剔除。
获取token的主要方法,controller层就不写了,自己定义
@Autowired
private UserService userService;
@Resource
private RedisTemplate<String,Object> redisTemplate;
private static final String PROXY_TOKEN = "proxy:auth:token:";
@Override
public TokenVO getToken(@Valid ClientVO clientVO, HttpServletRequest httpServletRequest) {
//todo 验证clent_id, clent_secret 是否正确
verifyClient(clientVO);
String token = TokenUtil.generateToken(clientVO.getClient_id());
UserDetails userDetails = this.userService.loadUserByUsername(clientVO.getClient_id().split("_")[0]);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
redisTemplate.opsForValue().set(PROXY_TOKEN + token, JSON.toJSONString(userDetails),6, TimeUnit.HOURS);
return TokenVO.builder().access_token(token).token_type("bearer").build();
}
public void verifyClient(ClientVO clientVO) {
/// TODO: 2022/7/29 从redis中根据client_id 读取client_secret,判断是否匹配
}
调试
访问获取token的方法,我这里client_id,client_secret是模拟的客户端模式,大家也可以模拟用户名和密码模式
不带token访问业务功能api
不带token,通过AccessDeniedHandler处理,抛出我们自定义的异常没有权限
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON);
response.setStatus(403);
ExceptionResponse exceptionResponse = ExceptionResponse.builder().msg("Forbidden: " + e.getMessage()).code("403").build();
response.getWriter().write(JSON.toJSONString(exceptionResponse));
}
}
带上token访问,正常返回信息
待办
代码里还有好多todo list,需要加强权限验证,不过这里主要记录security的使用