如果用户通过Spring Security进行登录,通常会涉及到以下四个类,并按照如下顺序进行处理:
1.WebSecurityConfigurerAdapter:首先会调用WebSecurityConfigurerAdapter中的configure(HttpSecurity http)方法,用于配置哪些URL需要被拦截,哪些URL不需要被拦截。如果用户访问一个受保护的URL,则Spring Security会重定向到登录页面。
2.UserDetailsService:当用户填写登录表单并点击“登录”按钮时,Spring Security会将表单中的用户名和密码传递给UserDetailsService类,以便验证用户是否有效。如果用户信息是有效的,该方法会返回一个实现了UserDetails接口的对象,表示已验证的用户。
3.AuthorizationServerConfigurerAdapter:如果用户信息是有效的,则Spring Security会创建一个OAuth2令牌并将其发送给客户端。这一步骤涉及到AuthorizationServerConfigurerAdapter类,其中定义了授权服务器的详细信息,包括客户端ID、客户端密钥、授权类型等等。
4.TokenConfig:最后,Spring Security会使用TokenConfig类来设置令牌的存储方式、过期时间等其他配置细节。在该类中,我们可以指定使用哪种TokenStore、TokenEnhancer等。
.总之,以上四个步骤结合在一起,可以实现用户认证和授权,并生成一个OAuth2令牌,一般都会使用jwt令牌来替代OAuth2令牌。
JWT是一个基于JSON格式的令牌,其中包含了关于用户、授权和其他元数据的声明。JWT可以使用私钥进行签名,确保其完整性和真实性,还可以使用公钥进行验证。由于JWT是自包含的,因此它们可以在不同系统之间轻松地传递,并且可以存储在客户端中而无需依赖服务器。另外,由于JWT已经被签名,所以它们通常不需要与授权服务器通信来验证其有效性,这使得JWT更具扩展性和灵活性
模块该如何吃透
权限认证
1,功能实现
业务功能实现:用户名密码登录、二维码登录,第三方用户登录、手机短信登录、用户、角色、权限管理和分配
●技术方案支撑:RBAC模型、Spring Security 或Apache Shiro
2,常见的问题
token刷新问题、密码的加密和解密、XSS防跨站攻击
3,权限系统设计
可扩展性、高可用性、通用性
手机短信登录
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//获取手机号
String phone = user.getPhone();
if(StringUtils.isNotEmpty(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);
//调用阿里云提供的短信服务API完成发送短信
//SMSUtils.sendMessage("瑞吉外卖","",phone,code);
//将生成的验证码缓存到Redis中,并且设置有效期为5分钟
redisTemplate.opsForValue().set(phone,code,5,TimeUnit.MINUTES);
return R.success("手机验证码短信发送成功");
}
return R.error("短信发送失败");
}
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
/**
* 短信发送工具类
*/
public class SMSUtils {
/**
* 发送短信
* @param signName 签名
* @param templateCode 模板
* @param phoneNumbers 手机号
* @param param 参数
*/
public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(phoneNumbers);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\""+param+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println("短信发送成功");
}catch (ClientException e) {
e.printStackTrace();
}
}
}
第三方登录流程(以QQ登录其他系统为例子)
1.第三方QQ用户发起登录请求:当第三方QQ用户选择使用QQ登录我们的系统时,用户通过点击相关登录链接或按钮触发登录请求。
2.跳转至QQ登录页面:系统将生成一个包含授权请求的URL,并将用户重定向到QQ的登录页面。用户在该页面进行QQ账号的登录验证。
3.用户授权:登录成功后,QQ向用户展示所需授权信息的权限列表,用户同意授权请求,允许我们的系统访问其相关信息。
4.获取授权凭证code:QQ登录页面在用户授权成功后,将会将一个授权凭证code返回给我们的系统。该code是临时有效的,通常只能使用一次。
5.后端获取access token:我们的系统收到QQ返回的授权凭证code后,使用该code与QQ的授权服务器交互,并通过OAuth2协议获取访问令牌(access token)。此令牌用于后续对QQ API的调用进行认证。
6.获取用户信息:系统使用获得的access token与QQ的API服务器通信,请求获取用户的相关信息,如昵称、头像等。
7.验证用户身份:获取到用户信息后,系统可以根据自身业务逻辑对用户进行验证。此时可以使用Spring Security提供的UserDetailsService接口实现,该接口负责从数据库或其他数据源中获取用户的详细信息。
8.生成JWT令牌:如果用户已在系统中注册并通过验证,系统会生成一个JWT令牌。这里可以利用UserDetailsService从数据库中获取的用户信息,包括用户名、角色等,进行令牌的生成和签名。
9.返回JWT令牌:系统将生成的JWT令牌返回给前端客户端。客户端可以将令牌存储在Cookie或本地存储中,以便后续请求时携带。
10.后续请求的认证与授权:在用户完成登录后,其后续的请求将携带JWT令牌作为身份凭证。每次请求到达后端时,Spring Security会使用配置的JWT验证器来验证令牌的有效性,并从令牌中提取用户信息。
11.UserDetailsService的使用:在验证过程中,Spring Security使用UserDetailsService接口来加载用户的详细信息。该接口包含loadUserByUsername方法,通过用户名从数据库或其他数据源中加载用户信息。
token刷新问题,可以直接通过配置来解决
1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.新建配置类TokenConfig
//令牌管理服务 public class TokenConfig
@Bean(name="authorizationServerTokenServicesCustom")
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
密码的加密和密码匹配
在WebSecurityConfigurerAdapter的子类中加入
@Autowired
PasswordEncoder passwordEncoder;
boolean matches = passwordEncoder.matches(inputPassword, password);
//public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
XSS防跨站攻击
XSS(跨站脚本攻击)是一种常见的Web应用程序安全漏洞,攻击者通过在受害者访问的网页中注入恶意脚本,从而达到获取用户敏感信息、窃取cookie等目的。
其中一个常见的方法是使用Thymeleaf等前端模板引擎,在显示用户输入内容时对其进行转义,以防止恶意脚本的注入。在Spring Security中,可以通过配置HttpSecurity对象来启用XSS保护机制,例如:
//WebSecurityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.xssProtection()
.block(true)
.xssProtectionEnabled(true);
}
这将启用浏览器内置的XSS保护机制,如果检测到恶意脚本,则会自动将其阻止。
另外,Spring Security还提供了许多其他的安全策略和配置选项,如使用Content-Security-Policy(CSP)来限制页面中可执行的脚本来源,使用HttpOnly属性来限制cookie的访问权限等。这些机制可以帮助保护Web应用程序免受XSS攻击。
jwt令牌springsecurity框架默认会校验有无令牌和令牌的合法性
每次都会携带令牌,可以通过一下获得(令牌一般都会扩展存储一些用户信息)
Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principalObj instanceof String) {
//取出用户身份信息
String principal = principalObj.toString();
//将json转成对象
XcUser user = JSON.parseObject(principal, XcUser.class);
return user;
}
但是有一些业务有的接口需要登录有的接口不需要登录就可访问,如何解决?
/**
* @Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/**","/course/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll();//其余放行
}
RBAC模型,分为资源和角色型(资源性权限扩展性强)
在服务中配置
package com.xuecheng.content.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @description RBAC资源服务配置
* @author Mr.M
* @date 2022/10/18 16:33
* @version 1.0
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
/**
*
*/
//资源服务标识
public static final String RESOURCE_ID = "xuecheng-plus";
@Autowired
TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/r/**","/course/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll();//其余放行
}
//RBAC角色服务配置
// @Override
// public void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests()
// .antMatchers("/api/admin/**").hasRole("ADMIN")
// .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
// .anyRequest().authenticated();
// }
}