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的使用

posted @ 2022-07-31 13:26  Levcon  阅读(63)  评论(0编辑  收藏  举报