当前位置:   article > 正文

Spring Boot 访问安全之认证和鉴权_spring 权限认证 排除某个路径

spring 权限认证 排除某个路径

在web应用中有大量场景需要对用户进行安全校,一般人的做法就是硬编码的方式直接埋到到业务代码中,但可曾想过这样做法会导致代码不够简洁(大量重复代码)、有个性化时难维护(每个业务逻辑访问控制策略都不相同甚至差异很大)、容易发生安全泄露(有些业务可能不需要当前登录信息,但被访问的数据可能是敏感数据由于遗忘而没有受到保护)。为了更安全、更方便的进行访问安全控制,我们可以想到的就是使用springmvc的拦截器(HandlerInterceptor),但其实更推荐使用更为成熟的spring security来完成认证和鉴权。

拦截器

拦截器HandlerInterceptor确实可以帮我们完成登录拦截、或是权限校验、或是防重复提交等需求。其实基于它也可以实现基于url或方法级的安全控制。如果你对spring mvc的请求处理流程相对的了解,它的原理容易理解,具体可以参阅我之前的分享

  1. public interface HandlerInterceptor {
  2. /**
  3. * Intercept the execution of a handler. Called after HandlerMapping determined
  4. * an appropriate handler object, but before HandlerAdapter invokes the handler.
  5. *
  6. * 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理
  7. *
  8. * handler:controller内的方法,可以通过HandlerMethod method= ((HandlerMethod)handler);获取到@RequestMapping
  9. */
  10. boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
  11. /**
  12. * Intercept the execution of a handler. Called after HandlerAdapter actually
  13. * invoked the handler, but before the DispatcherServlet renders the view.
  14. *
  15. * 在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView
  16. */
  17. void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
  18. /**
  19. * Callback after completion of request processing, that is, after rendering
  20. * the view. Will be called on any outcome of handler execution, thus allows
  21. * for proper resource cleanup.
  22. *
  23. * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面)
  24. *
  25. */
  26. void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
  27. }
  28. //你可以基于有些url进行拦截
  29. @Configuration
  30. public class UserSecurityInterceptor extends WebMvcConfigurerAdapter {
  31. @Override
  32. public void addInterceptors(InterceptorRegistry registry) {
  33. String[] securityUrls = new String[]{"/**"};
  34. String[] excludeUrls = new String[]{"/**/esb/**", "/**/dictionary/**"};
  35. registry.addInterceptor(userLoginInterceptor()).excludePathPatterns(excludeUrls).addPathPatterns(securityUrls);
  36. super.addInterceptors(registry);
  37. }
  38. /** fixed: url中包含// 报错
  39. * org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized.
  40. * @return
  41. */
  42. @Bean
  43. public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
  44. DefaultHttpFirewall firewall = new DefaultHttpFirewall();
  45. firewall.setAllowUrlEncodedSlash(true);
  46. return firewall;
  47. }
  48. @Bean
  49. public AuthInterceptor userLoginInterceptor() {
  50. return new AuthInterceptor();
  51. }
  52. public class AuthInterceptor implements HandlerInterceptor {
  53. public Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
  54. @Autowired
  55. private ApplicationContext applicationContext;
  56. public AuthInterceptor() {
  57. }
  58. @Override
  59. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  60. LoginUserInfo user = null;
  61. try {
  62. user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
  63. } catch (Exception e) {
  64. logger.error("从SSO登录信息中获取用户信息失败! 详细错误信息:%s", e);
  65. throw new ServletException("从SSO登录信息中获取用户信息失败!", e);
  66. }
  67. String[] profiles = applicationContext.getEnvironment().getActiveProfiles();
  68. if (!Arrays.isNullOrEmpty(profiles)) {
  69. if ("dev".equals(profiles[0])) {
  70. return true;
  71. }
  72. }
  73. if (user == null || UserUtils.ANONYMOUS_ROLE_ID.equals(user.getRoleId())) {
  74. throw new ServletException("获取登录用户信息失败!");
  75. }
  76. return true;
  77. }
  78. @Override
  79. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  80. }
  81. @Override
  82. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  83. }
  84. }
  85. }

认证

确认一个访问请求发起的时候背后的用户是谁,他的用户信息是怎样的。在spring security 里面认证支持很多种方式,最简单的就是用户名密码,还有LDAP、OpenID、CAS等等。

而在我们的系统里面,用户信息需要通过kxtx-sso模块进行获取。通过sso认证比较简单,就是要确认用户是否通过会员系统登录,并把登录信息包装成授权对象放到SecurityContext中,通过一个filter来完成:

  1. @Data
  2. @EqualsAndHashCode(callSuper = false)
  3. public class SsoAuthentication extends AbstractAuthenticationToken {
  4. private static final long serialVersionUID = -1799455508626725119L;
  5. private LoginUserInfo user;
  6. public SsoAuthentication(LoginUserInfo user) {
  7. super(null);
  8. this.user = user;
  9. }
  10. @Override
  11. public Object getCredentials() {
  12. return "kxsso";
  13. }
  14. @Override
  15. public Object getPrincipal() {
  16. return user;
  17. }
  18. @Override
  19. public String getName() {
  20. return user.getName();
  21. }
  22. }
  23. public class SsoAuthenticationProcessingFilter extends OncePerRequestFilter {
  24. @Override
  25. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  26. throws ServletException, IOException {
  27. LoginUserInfo user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
  28. SsoAuthentication auth = new SsoAuthentication(user );
  29. SecurityContextHolder.getContext().setAuthentication(auth);
  30. filterChain.doFilter(request, response);
  31. }
  32. }
  33. @Component
  34. public class SsoAuthenticationProvider implements AuthenticationProvider {
  35. @Value("${env}")
  36. String env;
  37. @Override
  38. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  39. LoginUserInfo loginUserInfo = (LoginUserInfo) authentication.getPrincipal();
  40. /*
  41. * DEV环境允许匿名用户访问,方便调试,其他环境必须登录。
  42. */
  43. if (!UserUtils.ANONYMOUS_ROLE_ID.equals(loginUserInfo.getRoleId()) || "dev".equals(env)) {
  44. authentication.setAuthenticated(true);
  45. } else {
  46. throw new BadCredentialsException("请登录");
  47. }
  48. return authentication;
  49. }
  50. @Override
  51. public boolean supports(Class<?> authentication) {
  52. return SsoAuthentication.class.equals(authentication);
  53. }
  54. }
  55. @Configuration
  56. @EnableWebSecurity
  57. @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
  58. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  59. protected void configure(HttpSecurity http) throws Exception {
  60. // 关闭session
  61. http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
  62. // 允许访问所有URL,通过方法保护的形式来限制访问。
  63. http.authorizeRequests().anyRequest().permitAll();
  64. // 注册sso filter
  65. http.addFilterBefore(ssoAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
  66. }
  67. @Bean
  68. SsoAuthenticationProcessingFilter ssoAuthenticationProcessingFilter() {
  69. return new SsoAuthenticationProcessingFilter();
  70. }
  71. }

鉴权

控制一个功能是否能被当前用户访问,对不符合要求的用户予以拒绝。spring security 主要有两种控制点:

  1. 基于请求路径的:控制某一URL模式必须符合某种要求;
  2. 基于方法的:控制某一方法必须符合某种要求;

而控制形式就比较多样化了:

  1. 代码配置;
  2. xml配置;
  3. 注解控制;
  4. el表达式;
  5. 自定义访问控制器;

目前鉴权的需求比较简单:登录允许访问,未登录禁止访问。因此可以定义了一个切面,控制所有需要安全控制的Controller。

spring security 提供了一些注解:

@PreAuthorize

控制一个方法是否能够被调用,业务方法(HandlerMethod )的前置处理,比如:

@PreAuthorize("#id<10")限制只能查询Id小于10的用户

@PreAuthorize("principal.username.equals(#username)")限制只能查询自己的信息

@PreAuthorize("#user.name.equals('abc')")限制只能新增用户名称为abc的用户

@PostAuthorize

业务方法调用完之后进行权限检查,后置处理,比如:

@PostAuthorize("returnObject.id%2==0")

public User find(int id) {}

返回值的id是偶数则表示校验通过,否则表示校验失败,将抛出AccessDeniedException

@PreFilter

对集合类型的参数进行过滤,比如:

对集合ids中id不为偶数的进行移除
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {}

@PostFilter

对集合类型的返回值进行过滤,比如:

将对返回结果中id不为偶数的list中的对象进行移除

@PostFilter("filterObject.id%2==0")
public List<User> findAll() {}

@AuthenticationPrincipal解决在业务方法内对当前用户信息的方法
  1. @Aspect
  2. @Component
  3. public class InControllerAspect {
  4. @Autowired
  5. BeforeInControllerMethods beforeInMethods;
  6. @Pointcut("execution(public * com.kxtx.oms.portal.controller.in.*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
  7. public void hp() {
  8. };
  9. @Before("hp()")
  10. public void befor() {
  11. beforeInMethods.before();
  12. }
  13. }
  14. @Component
  15. public class BeforeInControllerMethods {
  16. //@PreAuthorize("authenticated")要求所有访问此方法的用户必须登录
  17. @PreAuthorize("authenticated")
  18. public void before() {
  19. }
  20. }
  21. //用户信息获取
  22. @RequestMapping("/order/submit")
  23. public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
  24. // .. find messages for this user and return them ...
  25. }

是不是有点复杂,复杂的是表现形式,实际上需要真正理解它的目的(为了要解决什么问题)。

参考资料

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/951006
推荐阅读
相关标签
  

闽ICP备14008679号