首页 文章 精选 留言 我的

精选列表

搜索[springsecurity],共275篇文章
优秀的个人博客,低调大师

SpringSecurity原理剖析与权限系统设计

Spring Secutity和Apache Shiro是Java领域的两大主流开源安全框架,也是权限系统设计的主要技术选型。本文主要介绍Spring Secutity的实现原理,并基于Spring Secutity设计基于RBAC的权限系统。 一、技术选型 为何把Spring Secutity作为权限系统的技术选型,主要考虑了以下几个方面: 数据鉴权的能力:Spring Secutity支持数据鉴权,即细粒度权限控制。 Spring生态基础:Spring Secutity可以和Spring生态无缝集成。 多样认证能力:Spring Secutity支持多样认证方式,如预认证方式可以与第三方认证系统集成。 二、核心架构 权限系统一般包含两大核心模块:认证(Authentication)和鉴权(Authorization)。 认证:认证模块负责验证用户身份的合法性,生成认证令牌,并保存到服务端会话中(如TLS)。 鉴权:鉴权模块负责从服务端会话内获取用户身份信息,与访问的资源进行权限比对。 官方给出的Spring Security的核心架构图如下: 核心架构解读: AuthenticationManager:负责认证管理,解析用户登录信息(封装在Authentication),读取用户、角色、权限信息进行认证,认证结果被回填到Authentication,保存在SecurityContext。 AccessDecisionManager:负责鉴权投票表决,汇总投票器的结果,实现一票通过(默认)、多票通过、一票否决策略。 SecurityInterceptor:负责权限拦截,包括Web URL拦截和方法调用拦截。通过ConfigAttributes获取资源的描述信息,借助于AccessDecisionManager进行鉴权拦截。 SecurityContext:安全上下文,保存认证结果。提供了全局上下文、线程继承上下文、线程独立上下文(默认)三种策略。 Authentication:认证信息,保存用户的身份标示、权限列表、证书、认证通过标记等信息。 SecuredResource:被安全管控的资源,如Web URL、用户、角色、自定义领域对象等。 ConfigAttributes:资源属性配置,描述安全管控资源的信息,为SecurityInterceptor提供拦截逻辑的输入。 三、设计原理 通过对源码的分析,我把Spring Security的核心领域模型设计整理如下: 全局抽象模型解读: 配置:AuthenticationConfiguration负责认证系统的全局配置,GlobalMethodSecurityConfiguration负责方法调用拦截的全局配置。 构建:AuthenticationConfiguration通过AuthenticationManagerBuilder构建认证管理器AuthenticationManager,GlobalMethodSecurityConfiguration会自动初始化AbstractSecurityInterceptor进行方法调用拦截。 Web拦截:HttpSecurity对Web进行安全配置,内置了大量GenericFilterBean过滤器对URL进行拦截。负责认证的过滤器会通过AuthenticationManager进行认证,并将认证结果保存到SecurityContext。 方法拦截:Spring通过AOP技术(cglib/aspectj)对标记为@PreAuthorize、@PreFilter、@PostAuthorize、@PostFilter等注解的方法进行拦截,通过AbstractSecurityInterceptor调用AuthenticationManager进行身份认证(如果必要的话)。 认证:认证管理器AuthenticationManager内置了多种认证器AuthenticationProvider,只要其中一个认证通过,认证便成功。不同的AuthenticationProvider获取各自需要的信息(HTTP请求、数据库查询、远程服务等)进行认证,认证结果全部封装在Authentication。需要加载用户、角色、权限信息的认证器(如密码认证、预认证等)需要对接UserDetailsManager接口实现用户CRUD功能。 鉴权:权限拦截器AbstractSecurityInterceptor通过读取不同的SecurityMetadataSource加载需要被鉴权资源的描述信息ConfigAttribute,然后把认证信息Authentication、资源描述ConfigAttribute、资源对象本身传递给AccessDecisionManager进行表决。AccessDecisionManager内置了多个投票器AccessDecisionVoter,投票器会将鉴权信息中的ConfigAttribute转换为SpringEL的格式,通过表达式处理器SecurityExpressionHandler执行基于表达式的鉴权逻辑,鉴权逻辑会通过反射的方式转发到SecurityExpressionRoot的各个操作上去。 定制:通过WebSecurityConfigureAdapter可以定制HTTP安全配置HttpSecurity和认证管理器生成器AuthenticationManagerBuilder;通过AbstractPreAuthenticatedProcessingFilter可以定制预认证过滤器;通过UserDetailsManager和UserDetails接口可以对接自定义数据源;通过GrantedAuthority定制权限信息;通过PermissionEvaluator可以定制自定义领域模型的访问控制逻辑。 四、应用集成 理清Spring Security的定制点后,就可以在系统内部集成Spring Security了。 这里使用预认证的方式,以适配第三方认证系统。AbstractPreAuthenticatedProcessingFilter提供了预认证的扩展点,基于该抽象类实现一个自定义认证过滤器。 publicclassMyPreAuthFilterextendsAbstractPreAuthenticatedProcessingFilter{ @Override protectedObjectgetPreAuthenticatedPrincipal(HttpServletRequestrequest){ //从第三方系统获取用户ID returnuserId; } @Override protectedObjectgetPreAuthenticatedCredentials(HttpServletRequestrequest){ return""; } } Spring Security会根据预认证过滤器getPreAuthenticatedPrincipal返回的用户ID信息,加载用户角色等初始信息。这里需要实现UserDetailsManager接口,提供用户信息管理器。 @Service publicclassMyUserManagerimplementsUserDetailsManager{ @Override publicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{ //从数据库加载用户信息 returnuser; } //其他管理接口 } UserDetails内包含了GrantedAuthority接口类型的权限信息抽象,一般可以基于它自定义角色和权限。Spring Security使用一种接口形式表达角色和权限,角色和权限的差别是角色的ID是以"ROLE_"为前缀。 publicclassMyRoleimplementsGrantedAuthority{ privatefinalStringrole; @Override publicStringgetAuthority(){ return"ROLE_"+role; } } publicclassMyAuthorityimplementsGrantedAuthority{ privatefinalStringauthority; @Override publicStringgetAuthority(){ returnauthority; } } 接下来注册自定义认证过滤器和用户管理器,这里需要实现WebSecurityConfigurerAdapter进行Web安全配置。 @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true,mode=AdviceMode.PROXY) publicclassMySecurityConfigextendsWebSecurityConfigurerAdapter{ @Autowired UserDetailsManageruserDetailsManager; @Bean protectedAuthenticationProvidercreatePreAuthProvider(){ //注册用户管理器 PreAuthenticatedAuthenticationProviderprovider=newPreAuthenticatedAuthenticationProvider(); provider.setPreAuthenticatedUserDetailsService(newUserDetailsByNameServiceWrapper<>(userDetailsManager)); returnprovider; } @Override protectedvoidconfigure(HttpSecurityhttp)throwsException{ //注册预认证过滤器 http.addFilter(newMyPreAuthFilter(authenticationManager())); } } 这样,最简单的Spring Security框架集成内系统内部已经完成了。在系统的任意服务接口上可以使用如下方式进行鉴权。 publicinterfaceMyService{ @PreAuthorize("hasAuthority('QUERY')") ObjectgetById(Stringid); @PreAuthorize("hasRole('ADMIN')") voiddeleteById(Stringid); } PreAuthorize注解表示调用前鉴权,Spring使用默认使用动态代理技术生成鉴权逻辑。注解内配置了SpringEL表达式来定制鉴权方式。上述代码中,hasAuthority会检查用户是否有QUERY权限,hasRole会检查用户是否有ADMIN角色。 使用动态代理的方式进行AOP,只允许在接口层面进行权限拦截,如果想在任意的方法上进行权限拦截,那么就需要借助于AspectJ的方式进行AOP。首先将注解EnableGlobalMethodSecurity的mode设置为AdviceMode.ASPECTJ,然后添加JVM启动参数,这样就可以在任意方法上使用Spring Security的注解了。 javaagent:/path/to/org/aspectj/aspectjweaver/1.9.4/aspectjweaver-1.9.4.jar 以上还是只是以用户的身份信息(角色/权限)进行权限,灵活度有限,也发挥不了Spring Security的数据鉴权的能力。要使用数据鉴权,需要实现一个Spring Bean。 @Component publicclassMyPermissionEvaluatorimplementsPermissionEvaluator{ @Override publicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){ //自定义数据鉴权 returnfalse; } @Override publicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){ //自定义数据鉴权 returnfalse; } } PermissionEvaluator会被自动注册到Spring Security框架,并允许在注解内使用如下方式进行鉴权。 @PreAuthorize("hasPermission(#id,'QUERY')") Objectfunc1(Stringid){ } @PreAuthorize("hasPermission(#id,'TABLE','QUERY')") Objectfunc2(Stringid){ } 其中,func1的注解表示校验用户是否对id有QUERY权限,代码逻辑路由到MyPermissionEvaluator的第一个接口。func2的注解表示校验用户是否对TABLE类型的id有QUERY权限,代码逻辑路由到MyPermissionEvaluator的第二个接口。PermissionEvaluator提供了权限系统中数据鉴权的扩展点,稍后会描述如何利用该扩展点定制基于RBAC的权限系统。 五、权限系统 构建基于RBAC(Role Based Access Control)的权限系统,需要明确用户、角色、权限、资源这几个核心的概念类的含义和它们之间的关系。 资源:权限系统内需要安全控制的客体,一般是系统内的数据或功能。 权限:描述了资源上的操作抽象,一般是一种动作。 授权:是权限和资源的组合,表示对资源的某一个操作。 角色:描述了一组授权的集合,表示一类特殊概念的功能集。 用户:权限系统的主体,一般是当前系统的访问用户,用户可以拥有多种角色。 以下是我们设计的基于RABC的权限核心领域模型: 一般情况下,系统内需要权限管控的资源是无法用户自定义的,因为资源会耦合大量的业务逻辑,所以我们提供了自 资源工厂,通过配置化的方式构建业务模块所需的资源。而用户、角色、权限,以及授权记录都是可以通过相应的管理器进行查询更新。 另外,资源抽象允许表达资源的继承和组合关系,继而表达更复杂的资源模型,资源统一鉴权的流程为: 执行鉴权时,首先看资源是原子资源还是组合资源。 对于原子资源,先查询是否有授权记录,再查看角色预授权是否包含当前授权,存在一种便成功。 没有授权记录和角色预授权的原子资源,尝试用父资源(如果有的话)代替鉴权,否则鉴权失败。 对于组合资源,先进行资源展开,获取子资源列表。 遍历子资源列表,并依次对子资源进行鉴权,子资源鉴权结果汇总后,即组合资源鉴权结果。 综上,基于统一资源抽象和资源配置化构建,可以实现资源的统一构建,继而实现统一鉴权。 六、总结回顾 本文从Spring Security的架构和原理出发,描述了开源安全框架对于认证和鉴权模块的设计思路和细节。并提供了系统内集成Spring Security的方法,结合RBAC通用权限系统模型,讨论了统一资源构建和统一鉴权的设计和实现。如果你也需要设计一个新的权限系统,希望本文对你有所帮助。

优秀的个人博客,低调大师

【SpringBoot技术专题】「JWT技术专区」SpringSecurity整合JWT授权和认证实现

JWT基本概念 JWT,即 JSON Web Tokens(RFC 7519),是一个广泛用于验证 REST APIs 的标准。虽说是一个新兴技术,但它却得以迅速流行。 JWT的验证过程是: 前端(客户端)首先发送一些凭证来登录(我们编写的是 web 应用,所以这里使用用户名和密码来做验证)。 后端(服务端)这里指Spring应用校验这些凭证,如果校验通过则生成并返回一个 JWT。 客户端需要在请求头的Authorization字段中以 “Bearer TOKEN” 的形式携带获取到的token,服务端会检查这个token是否可用并决定授权访问或拒绝请求。 token中可能保存了用户的角色信息,服务端可以根据用户角色来确定访问权限。 实现 我们来看一下在实际的 Spring 项目中是如何实现JWT登录和保存机制的。 依赖 下面是我们示例代码的 Maven 依赖列表,注意,截图中并未包含Spring Boot、Hibernate等核心依赖(你需要自行添加)。 用户模型 创建一个包含保存用户信息、基于用户名和密码验证用户权限功能的 controller。 创建一个名为 User 的实体类,它是数据库中 USER 表的映射。需要的话,可以在其中添加其他属性。 还需要定义一个 UserRepository 类来保存用户信息,重写其 findByUsername 方法,在验证过程中会用到。 public interface UserRepository extends JpaRepository<User, String>{ User findByUsername(String username); } 千万不能在数据库中保存明文密码,因为很多用户喜欢在各种网站上使用相同的密码。 哈希算法有很多,BCrypt是最常用的之一,它也是推荐用于安全加密的算法。关于这个话题的更多内容,可以查看 这篇文章。 为了加密密码,我们在 @bean 注解标记的主类中定义一个 BCrypt Bean,如下所示: @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } 加密密码的时候将会调用这个Bean里面的方法。 创建一个名为 UserController 的类,为其添加 @RestController 注解并定义路由映射。 在这个应用中,我们接收前端传入的 UserDto 对象来保存用户信息。你也可以选择在 @RequestBody 参数中接收 User 对象。 @RestController @RequestMapping("/api/services/controller/user") @AllArgsConstructor public class UserController { private UserService userService; @PostMapping() public ResponseEntity<String> saveUser(@RequestBody UserDto userDto) { return new ResponseEntity<>(userService.saveDto(userDto), HttpStatus.OK); } } 我们使用之前定义的 BCrypt Bean 来加密传入的 UserDto 对象的 password 字段。这个操作也可以在 controller 之中执行,但是把逻辑操作集中到 service 类中是更好的做法。 @Transactional(rollbackFor = Exception.class) public String saveDto(UserDto userDto) { userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword())); return save(new User(userDto)).getId(); } 验证过滤器 需要通过权限验证来确定用户的真实身份。这里我们使用经典的【用户名-密码对】的形式来完成。 验证步骤: 创建继承 UsernamePasswordAuthenticationFilter 的验证过滤器 创建继承 WebSecurityConfigurerAdapter 的安全配置类并应用过滤器 验证过滤器的代码如下——也许你已经知道了,过滤器是 Spring Security 的核心。 public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; setFilterProcessesUrl("/api/services/controller/user/login"); } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { User creds = new ObjectMapper().readValue(req.getInputStream(), User.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( creds.getUsername(), creds.getPassword(), new ArrayList<>()) ); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException { String token = JWT.create() .withSubject(((User) auth.getPrincipal()).getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .sign(Algorithm.HMAC512(SECRET.getBytes())); String body = ((User) auth.getPrincipal()).getUsername() + " " + token; res.getWriter().write(body); res.getWriter().flush(); } } Spring Security 默认使用继承了 UsernamePasswordAuthenticationFilter 的子类进行密码验证 ,我们可以在其中编写自定义的验证逻辑。 我们在构造函数中调用setFilterProcessesUrl 方法,设置默认登录地址。 如果删除这行代码,Spring Security 会生成一个默认的 “/login” 端点,我们可以不用在 controller 中显式地定义登录端点。 这行代码执行之后,我们的登录端点将被设置为 /api/services/controller/user/login,你可以根据自己的实际代码来设置。 我们重写了 UsernameAuthenticationFilter 类的 attemptAuthentication 和 successfulAuthentication 方法。 用户登录时会执行 attemptAuthentication方法,它会读取凭证信息、创建用户 POJO、校验凭证并授权。 我们传入用户名、密码以及一个空列表。我们还没有定义用户角色,所以把这个表示用户权限(角色)的列表留空就行。 如果验证成功,就会执行 successfulAuthentication 方法,它的参数由Spring Security自动注入。 attemptAuthentication返回Authentication对象,这个对象包含了我们传入的权限信息。 我们想在验证成功之后返回一个使用用户名、密钥和过期时间创建的 token。先定义SECRET和 EXPIRATION_DATE。 public class SecurityConstants { public static final String SECRET = "SECRET_KEY"; public static final long EXPIRATION_TIME = 900_000; // 15 mins public static final String TOKEN_PREFIX = "Bearer "; public static final String HEADER_STRING = "Authorization"; public static final String SIGN_UP_URL = "/api/services/controller/user"; } 创建一个类作为常量的容器,SECRET 的值可以任意设置,最佳的做法是在 hash 算法支持的范围内使用尽可能长的字符串。例如我们使用的是 HS256 算法,SECRET 字符串的最佳长度即为 256 bits/32 个字符。 超时时间设置为 15 分钟,这是防御暴力破解密码的最佳实践。此处使用的时间单位为毫秒。 验证过滤器准备好了,但还不可用,我们还要创建一个授权过滤器,再通过一个配置类来应用它们。 授权过滤器会校验 Authorization 请求头中的 token 是否存在及其可用性。在配置类中指明哪些端点需要使用这个过滤器。 授权过滤器 doFilterInternal 方法拦截请求并校验 Authorization 请求头,如果不存在或者它的值不是以 “BEARER” 开头,则直接转到下一个过滤器。 如果这个请求头携带了合法的值,会调用 getAuthentication 方法,校验这个 JWT,如果这个 token 是可用的,它会返回一个Spring内部使用的 token。 这个新生成的 token 会被保存在 SecurityContext 中,如果需要基于用户角色进行授权的话,可以向这个 token 传入用户权限。 过滤器都准备好了,现在要通过配置类把它们投入使用。 public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authManager) { super(authManager); } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { chain.doFilter(req, res); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(req); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(req, res); } // Reads the JWT from the Authorization header, and then uses JWT to validate the token private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader(HEADER_STRING); if (token != null) { // parse the token. String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes())) .build() .verify(token.replace(TOKEN_PREFIX, "")) .getSubject(); if (user != null) { // new arraylist means authorities return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); } return null; } return null; } 配置处理 给这个类添加 @EnableWebSecurity 注解,同时让它继承 WebSecurityConfigureAdapter 并实现自定义的安全逻辑。 自动注入之前定义的 BCrypt Bean,同时自动注入 UserDetailsService 用来获取用户账户信息。 最重要的是那个接收一个 HttpSecurity 对象作为参数的方法,其中声明了如何在各个端点中应用过滤器、配置了 CORS、放行了所有对注册接口的 POST 请求。 可以添加其他匹配器来基于 URL 模式和角色进行过滤,你也可以 查看 StackOverflow 上这个问题的相关示例。另一个方法配置了 AuthenticationManager 在登录校验时使用我们指定的编码器。 @EnableWebSecurity public class WebSecurity extends WebSecurityConfigurerAdapter { private UserDetailsServiceImpl userDetailsService; private BCryptPasswordEncoder bCryptPasswordEncoder; public WebSecurity(UserDetailsServiceImpl userService, BCryptPasswordEncoder bCryptPasswordEncoder) { this.userDetailsService = userService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().authorizeRequests() .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll() .anyRequest().authenticated() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) // this disables session creation on Spring Security .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); } @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues(); source.registerCorsConfiguration("/**", corsConfiguration); return source; } } 测试实现 发送一些请求来测试应用是否正常工作。 使用 GET 请求访问受保护的资源,服务端返回了 403 状态码。 这是程序设计预期的行为,因为我们没有在请求头中携带 token 信息。 现在创建一个用户: 发送一个携带了用户信息数据的 POST 请求,以创建用户。稍后将登陆这个账户来获取 token。 获取到 token 了,现在可以用这个 token 来访问受保护的资源。 在 Authorization 请求头中携带 token,就可以访问受保护的端点了。 总结 Spring 中实现 JWT 授权和密码认证的步骤,同时学习了如何安全地保存用户信息。 参考内容 How to Set Up Java Spring Boot JWT Authorization and

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册