文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Boot 访问安全之认证和鉴权详解

2024-04-02 19:55

关注

在web应用中有大量场景需要对用户进行安全校,一般人的做法就是硬编码的方式直接埋到到业务代码中,但可曾想过这样做法会导致代码不够简洁(大量重复代码)、有个性化时难维护(每个业务逻辑访问控制策略都不相同甚至差异很大)、容易发生安全泄露(有些业务可能不需要当前登录信息,但被访问的数据可能是敏感数据由于遗忘而没有受到保护)。

为了更安全、更方便的进行访问安全控制,我们可以想到的就是使用springmvc的拦截器(HandlerInterceptor),但其实更推荐使用更为成熟的spring security来完成认证和鉴权。

拦截器

拦截器HandlerInterceptor确实可以帮我们完成登录拦截、或是权限校验、或是防重复提交等需求。其实基于它也可以实现基于url或方法级的安全控制。

如果你对spring mvc的请求处理流程相对的了解,它的原理容易理解。


public interface HandlerInterceptor {
		
	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
 
		
	void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
 
		
	void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
//你可以基于有些url进行拦截
@Configuration
public class UserSecurityInterceptor extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] securityUrls = new String[]{"esbdictionary
    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
        DefaultHttpFirewall firewall = new DefaultHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        return firewall;
    }
 
    @Bean
    public AuthInterceptor userLoginInterceptor() {
        return new AuthInterceptor();
    }
 
    public class AuthInterceptor implements HandlerInterceptor {
        public Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
        @Autowired
        private ApplicationContext applicationContext; 
        public AuthInterceptor() {
        }
 
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            LoginUserInfo user = null;
            try {
                user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
            } catch (Exception e) {
                logger.error("从SSO登录信息中获取用户信息失败! 详细错误信息:%s", e);
                throw new ServletException("从SSO登录信息中获取用户信息失败!", e);
            }
 
            String[] profiles = applicationContext.getEnvironment().getActiveProfiles();
            if (!Arrays.isNullOrEmpty(profiles)) {
                if ("dev".equals(profiles[0])) {
                    return true;
                }
            }
            if (user == null || UserUtils.ANONYMOUS_ROLE_ID.equals(user.getRoleId())) {
                throw new ServletException("获取登录用户信息失败!");
            }
            return true;
        }
 
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
        }
 
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
        }
    }
}

认证

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

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


@Data
@EqualsAndHashCode(callSuper = false)
public class SsoAuthentication extends AbstractAuthenticationToken {
    private static final long serialVersionUID = -1799455508626725119L; 
    private LoginUserInfo user; 
    public SsoAuthentication(LoginUserInfo user) {
        super(null);
        this.user = user;
    }
 
    @Override
    public Object getCredentials() {
        return "kxsso";
    }
 
    @Override
    public Object getPrincipal() {
        return user;
    }
 
    @Override
    public String getName() {
        return user.getName();
    }
}
public class SsoAuthenticationProcessingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        LoginUserInfo user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
        SsoAuthentication auth = new SsoAuthentication(user );
        SecurityContextHolder.getContext().setAuthentication(auth);
        filterChain.doFilter(request, response);
    }
}
@Component
public class SsoAuthenticationProvider implements AuthenticationProvider {
 
    @Value("${env}")
    String env;
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LoginUserInfo loginUserInfo = (LoginUserInfo) authentication.getPrincipal();
        
        if (!UserUtils.ANONYMOUS_ROLE_ID.equals(loginUserInfo.getRoleId()) || "dev".equals(env)) {
            authentication.setAuthenticated(true);
        } else {
            throw new BadCredentialsException("请登录");
        }
        return authentication;
    }
 
    @Override
    public boolean supports(Class<?> authentication) {
        return SsoAuthentication.class.equals(authentication);
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
 
        // 允许访问所有URL,通过方法保护的形式来限制访问。
        http.authorizeRequests().anyRequest().permitAll();
 
        // 注册sso filter
        http.addFilterBefore(ssoAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
    }
 
    @Bean
    SsoAuthenticationProcessingFilter ssoAuthenticationProcessingFilter() {
        return new SsoAuthenticationProcessingFilter();
    }
}

鉴权

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

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

目前鉴权的需求比较简单:登录允许访问,未登录禁止访问。因此可以定义了一个切面,控制所有需要安全控制的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 解决在业务方法内对当前用户信息的方法

@Aspect
@Component
public class InControllerAspect {
    @Autowired
    BeforeInControllerMethods beforeInMethods;
 
    @Pointcut("execution(public * com.kxtx.oms.portal.controller.in.*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void hp() {
    };
 
    @Before("hp()")
    public void befor() {
        beforeInMethods.before();
    }
}
@Component
public class BeforeInControllerMethods {
    //@PreAuthorize("authenticated")要求所有访问此方法的用户必须登录
    @PreAuthorize("authenticated")
    public void before() {
    }
}
//用户信息获取
@RequestMapping("/order/submit")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
    // .. find messages for this user and return them ...
}

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

参考资料

mvc-authentication-principal

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯