Spring Security

Spring Security 笔记

Security 底层是一个过滤器链,主要分为以下三个

  • FilterSecurityInterceptor : 方法级的过滤器,位于过滤器链最底部
  • ExceptionTranslationFilter: 异常处理过滤器,处理认证授权过程中抛出的异常
  • UsernamePasswordAuthenticationFilter: 对 /login 的 Post 请求拦截,校验表单中的用户名和密码

加载过程

DelegatingFilterProxy: doFilter() => initDelegate() 此方法目的是拿到 FilterChainProxy => invokeDelegate() => FilterChainProxy.doFilter() => FilterChainProxy.doFilterInternal()

两个重要接口

UserDetailsService: 实现 loadUserByUsername() 方法,自定义逻辑控制用户认证(查数据库等操作)
PasswordEncoder: 数据加密接口,有三个方法

public interface PasswordEncoder {
    /**
     * 按照特定的解析规则进行密码解析
     */
    String encode(CharSequence var1);

    /**
     * 验证提交的原始密码和存储中的密码是否匹配
     * @param var1 要加密的密码
     * @param var2 存储的密码
     */
    boolean matches(CharSequence var1, String var2);

    /**
     * 如果解析的密码能再次解析且达到更安全的结果返回 true,否则返回 false 默认 false
     *
     */
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

注解使用

配置类上开启
@EnableGlobalMethodSecurity(securedEnabled = true,
prePostEnabled = true)
注解情况下,controller 方法上可用的注解,列如:

@Secured({"ROLE_xxx", "ROLE_yyy"})
@GetMapping("/hello")
...


// 用户具有某个角色,可以访问该路径,角色前必须加 "ROLE_···"
@Secured({"ROLE_xxx", "ROLE_yyy"})

// 在方法执行之前进行权限验证
@PreAuthorize("hasAnyAuthority('admin')")

// 在方法执行之后进行权限认证,适合验证带有返回值的权限
@PostAuthorize("hasAnyAuthority('admin')")

// 权限验证之前对传入方法的参数进行过滤
@PreFilter("filterObject.id%2 == 0 ")

// 权限验证过后对返回的数据进行过滤
@PostFilter("filterObject.userName == 'admin'")

认证过滤器和授权过滤器

UsernamePasswordAuthenticationFilter: 认证过滤器


/**
 * 继承 UsernamePasswordAuthenticationFilter
 * 主要重写其中的 attemptAuthentication() 方法
 * 和认证成功会调用的 successfulAuthentication()
 * 认证失败会调用的 unsuccessfulAuthentication()
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;  // Token 生成解析工具
    private RedisTemplate redisTemplate;

    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            // 获取表单提交的用户名和密码
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 认证成功
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws  IOException,ServletException {
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        // redis 存入用户权限列表
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
        Map map = new HashMap<String, String>();
        map.put("token", token);
        res.setStatus(HttpStatus.OK.value());
        res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        // 返回 Token 到前端
        new ObjectMapper().writeValue(res.getWriter(),map);
    }

    /**
     * 认证失败
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        Map map = new HashMap<String, Object>();
        // 错误信息设置
        map.put("message", "...");
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        // 返回错误信息
        new ObjectMapper().writeValue(response.getWriter(),map);
    }
}

BasicAuthenticationFilter: 授权过滤器

/**
 * 授权过滤器
 * 
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;  // Token 生成解析工具
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);    // 获取用户权限信息
        } catch (Exception e) {
            // response 返回错误信息
        }

        if (authentication != null) {
            // 权限信息不为空,放入权限上下文中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            // response 返回没有权限信息
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);
            // 从 redis 中获取用户权限列表
            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }

            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}

配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private TokenManager tokenManager;          // Token 生成、解析工具
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private RedisTemplate redisTemplate;

    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
                                  TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint()) // 未授权的统一处理放式
                .and().csrf().disable()                                 // 关闭 CSRF 防护
                .authorizeRequests()                                    // 设置不需要授权的访问路径
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/logout")                    // 退出路劲
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
                // 添加认证过滤器
                .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
                // 添加授权过滤器
                .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }

    /**
     * 密码处理
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
               );
    }
}

posted @ 2021-06-07 11:56  lggtt  阅读(374)  评论(0)    收藏  举报