柚子快報激活碼778899分享:java 簡歷項(xiàng)目知識,八股文
柚子快報激活碼778899分享:java 簡歷項(xiàng)目知識,八股文
關(guān)于簡歷項(xiàng)目
基本網(wǎng)絡(luò)知識
TCP協(xié)議
傳輸控制協(xié)議
三次握手
客戶機(jī)發(fā)送連接請求報文段到服務(wù)器,并進(jìn)入SYN_SENT狀態(tài),等待服務(wù)器確認(rèn)(SYN = 1,seq = x) 服務(wù)器收到連接請求報文,如果同意建立連接,向客戶機(jī)發(fā)回確認(rèn)報文段,并為該TCP連接分配TCP緩存和變量(SYN = 1,ACK = 1,seq = y,ack = x+1) 客戶機(jī)收到服務(wù)器的確認(rèn)報文段后,向服務(wù)器給出確認(rèn)報文段,并且也要給該連接分配緩存和變量,此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED(TCP連接成功)狀態(tài),完成三次握手
四次揮手
TCP客戶端發(fā)送一個FIN,用來關(guān)閉客戶端到服務(wù)器的數(shù)據(jù)傳輸 服務(wù)器收到這個FIN,它發(fā)回一個ACK,確認(rèn)序號為收到的序號+1。和SYN一樣,一個FIN將占用一個序號 服務(wù)器關(guān)閉客戶端的連接,發(fā)送一個FIN給客戶端 客戶端發(fā)回ACK報文確認(rèn),并將確認(rèn)序號設(shè)置為收到序號+1
TCP握手為什么不能是兩次
防止兩次握手的情況下已經(jīng)失效的連接請求報文段突然又傳送到服務(wù)端而產(chǎn)生錯誤
HTTP請求中的GET和POST方法的區(qū)別
主要區(qū)別在于GET方法是請求讀取由URL所標(biāo)志的信息,POST是給服務(wù)器添加信息。
報文:get請求放在url,post請求放在報文體中,部分瀏覽器對URL長度有限制
Cookie和Session的區(qū)別
cookie數(shù)據(jù)保存在客戶端,session數(shù)據(jù)保存在服務(wù)器端 都可以存放敏感信息 cookie和session都是用來跟蹤瀏覽器用戶身份的會話方式 cookie保存在客戶機(jī)硬盤上,session默認(rèn)被保存在服務(wù)器的一個文件夾里(不是內(nèi)存) session的運(yùn)行依賴sessionId,而sessionId是存放在cookie中的,也就是說,如果瀏覽器禁用了cookie,同時session也會失效,但可以通過URL傳遞sessionId session可以存放在文件、數(shù)據(jù)庫、內(nèi)存中都可以 用戶驗(yàn)證這種場合一般會用session
博客項(xiàng)目
前后端的技術(shù)棧
前端是vue,在最近的一次修改中使用echarts線性表格來展示某一時間段的文章瀏覽量。
后端使用springboot、springsecurity、swagger、mybatisplus、Redis等。
SpringSecurity核心功能
認(rèn)證(Authentication) : 驗(yàn)證用戶身份的過程 授權(quán)(Authorization):確認(rèn)用戶權(quán)限 防護(hù)攻擊:如跨域請求偽造CSRF和跨站腳本攻擊XSS Servlet API集成:與Java Servlet API無縫集成,提供web安全功能 可擴(kuò)展性:通過自定義組件和擴(kuò)展點(diǎn)可輕松擴(kuò)展springSecurity
springSecurity的架構(gòu)
由以下組件組成:
securityContextHolder:存儲與當(dāng)前線程關(guān)聯(lián)的安全上下文。 Authentication:表示用戶的認(rèn)證信息。 UserDetails:表示用戶的詳細(xì)信息。 AuthenticationManager:負(fù)責(zé)處理認(rèn)證請求。 AccessDecisionManager:負(fù)責(zé)授權(quán)決策 FilterChainProxy:負(fù)責(zé)處理Http請求的過濾器鏈。 SecurityFilterChain:由一系列安全過濾器組成的鏈。
什么是SecurityContextHolder
securityContextHolder是一個用于存儲與當(dāng)前線程相關(guān)的安全上下文的類,它使用ThreadLocal機(jī)制來存儲當(dāng)前用戶的認(rèn)證信息,如Authentication對象。
springsecurity登錄如何實(shí)現(xiàn)
@Service
public class LoginServiceImpl implements LoginServcie {
?
? ?@Autowired
? ?private AuthenticationManager authenticationManager;
? ?@Autowired
? ?private RedisCache redisCache;
?
? ?@Override
? ?public ResponseResult login(User user) {
? ? ? ?UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
? ? ? ?Authentication authenticate = authenticationManager.authenticate(authenticationToken);
? ? ? ?if(Objects.isNull(authenticate)){
? ? ? ? ? ?throw new RuntimeException("用戶名或密碼錯誤");
? ? ? }
? ? ? ?//使用userid生成token
? ? ? ?LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
? ? ? ?String userId = loginUser.getUser().getId().toString();
? ? ? ?String jwt = JwtUtil.createJWT(userId);
? ? ? ?//authenticate存入redis
? ? ? ?redisCache.setCacheObject("login:"+userId,loginUser);
? ? ? ?//把token響應(yīng)給前端
? ? ? ?HashMap
? ? ? ?map.put("token",jwt);
? ? ? ?return new ResponseResult(200,"登陸成功",map);
? }
}
HttpServletRequest request =new HttpSerletRequest();
String token = request.getHeader("token");
獲取Http請求頭中的token,解析獲取用戶ID
Clamis clamis = null;
userId = claims.getSubject();
token超時或非法就重新登錄。
去redis中找用戶,存入securityContextHolder中
放行,doFilter(自定義過濾器)
獲取userId生成token
JwtUtil.creatJwt(userId);
?
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
?
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
?
/**
* JWT工具類
*/
public class JwtUtil {
?
? ?//有效期為
? ?public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一個小時
? ?//設(shè)置秘鑰明文
? ?public static final String JWT_KEY = "Haluki";
?
? ?public static String getUUID(){
? ? ? ?String token = UUID.randomUUID().toString().replaceAll("-", "");
? ? ? ?return token;
? }
? ?
? ?/**
? ? * 生成jtw
? ? * @param subject token中要存放的數(shù)據(jù)(json格式)
? ? * @return
? ? */
? ?public static String createJWT(String subject) {
? ? ? ?JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設(shè)置過期時間
? ? ? ?return builder.compact();
? }
?
? ?/**
? ? * 生成jtw
? ? * @param subject token中要存放的數(shù)據(jù)(json格式)
? ? * @param ttlMillis token超時時間
? ? * @return
? ? */
? ?public static String createJWT(String subject, Long ttlMillis) {
? ? ? ?JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設(shè)置過期時間
? ? ? ?return builder.compact();
? }
?
? ?private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
? ? ? ?SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
? ? ? ?SecretKey secretKey = generalKey();
? ? ? ?long nowMillis = System.currentTimeMillis();
? ? ? ?Date now = new Date(nowMillis);
? ? ? ?if(ttlMillis==null){
? ? ? ? ? ?ttlMillis=JwtUtil.JWT_TTL;
? ? ? }
? ? ? ?long expMillis = nowMillis + ttlMillis;
? ? ? ?Date expDate = new Date(expMillis);
? ? ? ?return Jwts.builder()
? ? ? ? ? ? ? .setId(uuid) ? ? ? ? ? ? ?//唯一的ID
? ? ? ? ? ? ? .setSubject(subject) ? // 主題 可以是JSON數(shù)據(jù)
? ? ? ? ? ? ? .setIssuer("haluki") ? ? // 簽發(fā)者
? ? ? ? ? ? ? .setIssuedAt(now) ? ? ?// 簽發(fā)時間
? ? ? ? ? ? ? .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密算法簽名, 第二個參數(shù)為秘鑰
? ? ? ? ? ? ? .setExpiration(expDate);
? }
?
? ?/**
? ? * 創(chuàng)建token
? ? * @param id
? ? * @param subject
? ? * @param ttlMillis
? ? * @return
? ? */
? ?public static String createJWT(String id, String subject, Long ttlMillis) {
? ? ? ?JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設(shè)置過期時間
? ? ? ?return builder.compact();
? }
?
? ?public static void main(String[] args) throws Exception {
? ? ? ?String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
? ? ? ?Claims claims = parseJWT(token);
? ? ? ?System.out.println(claims);
? }
?
? ?/**
? ? * 生成加密后的秘鑰 secretKey
? ? * @return
? ? */
? ?public static SecretKey generalKey() {
? ? ? ?byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
? ? ? ?SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
? ? ? ?return key;
? }
? ?
? ?/**
? ? * 解析
? ? *
? ? * @param jwt
? ? * @return
? ? * @throws Exception
? ? */
? ?public static Claims parseJWT(String jwt) throws Exception {
? ? ? ?SecretKey secretKey = generalKey();
? ? ? ?return Jwts.parser()
? ? ? ? ? ? ? .setSigningKey(secretKey)
? ? ? ? ? ? ? .parseClaimsJws(jwt)
? ? ? ? ? ? ? .getBody();
? }
?
?
}
jwt是什么
JSON Web Token,目前最流行的跨域身份驗(yàn)證解決方案
為什么使用jwt
jwt的精髓在于去中心化,數(shù)據(jù)保存在客戶端里面。
jwt工作原理
在服務(wù)器身份驗(yàn)證后,生成一個JSON對象,并將其發(fā)回給用戶,之后,用戶與服務(wù)器通信時,用戶在請求中發(fā)回JSON對象,服務(wù)器將在生成對象時添加簽名。
jwt組成
頭部header、載荷payload、簽名signature。
文章訪問量沒必要用redis
博客主頁中的文章訪問量采用了redis技術(shù),為了更新的更加快速,減少查詢數(shù)據(jù)庫的次數(shù),減小數(shù)據(jù)庫壓力。但后來發(fā)現(xiàn)沒有必要,于是在博客后臺中圖表顯示瀏覽量就直接查詢數(shù)據(jù)庫了。
我使用了@Scheduled(cron = "0/5 * * * * ?")這個注解,每五秒種在redis中查詢一次瀏覽量數(shù)據(jù),使用stream流收集數(shù)據(jù),然后更新到數(shù)據(jù)庫中。
后臺中,文章某個時間段的瀏覽量
在設(shè)計方面,我使用文章id+每隔5分鐘的時間戳作為組合主鍵,用戶訪問文章時,以當(dāng)前時間上下5分鐘取整記錄到數(shù)據(jù)庫中。如果訪問量大就設(shè)計成時間戳%5min+文章Id+Count的方式記錄訪問量,計數(shù)的顆粒度是5分鐘,減小數(shù)據(jù)庫壓力,后臺可以查看任意時間段內(nèi)的文章瀏覽量。(顆粒度:數(shù)據(jù)的細(xì)化程度)
密碼加密算法
這個項(xiàng)目中,我使用了BCrypt加密算法對用戶密碼進(jìn)行加密處理。
為什么使用這個BC算法呢:這個算法對于攻擊人員來說需要消耗的計算成本高,安全性好。該算法會自動生成隨機(jī)鹽值??蓴U(kuò)展性高。適用于長密碼。簡單易用。 為什么不用最新的Argon2加密算法呢:Argon2加密算法很新,可能會導(dǎo)致不兼容的情況出現(xiàn),并且Argon2算法的計算成本高,高負(fù)載下可能產(chǎn)生性能問題。
密碼加密流程
pom
groupId:org.springframework.security
artifactId:spring-security-crypto
version:5.6.1
在配置文件中注入BCryptPasswordEncoder:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
? ?@Bean
?public PasswordEncoder passwordEncoder(){
? ? ?return new BCryptPasswordEncoder();
}
}
在需要使用密碼的地方調(diào)用passwordEncoder.encode()方法對密碼進(jìn)行加密
@Autowired
private PasswordEncoder passwordEncoder;
@Override public User register(User user){
? ?String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
?//...
return user;
}
圖片上傳
使用七牛云OSS存儲上傳的圖片,前端發(fā)起post請求,后端接收后處理上傳。
先添加七牛云的依賴,在配置文件里面輸入密鑰和私鑰以及bucket。后端響應(yīng)是否上傳成功,以及圖片的訪問鏈接
@SpringBootTest
@ConfigurationProperties(prefix = "oss")
public class OSSTest {
?
? ?private String accessKey;
? ?private String secretKey;
? ?private String bucket;
?
? ?public void setAccessKey(String accessKey) {
? ? ? ?this.accessKey = accessKey;
? }
?
? ?public void setSecretKey(String secretKey) {
? ? ? ?this.secretKey = secretKey;
? }
?
? ?public void setBucket(String bucket) {
? ? ? ?this.bucket = bucket;
? }
?
? ?@Test
? ?public void testOss(){
? ? ? ?//構(gòu)造一個帶指定 Region 對象的配置類
? ? ? ?Configuration cfg = new Configuration(Region.autoRegion());
? ? ? ?//...其他參數(shù)參考類注釋
?
? ? ? ?UploadManager uploadManager = new UploadManager(cfg);
? ? ? ?//...生成上傳憑證,然后準(zhǔn)備上傳
// ? ? ? String accessKey = "your access key";
// ? ? ? String secretKey = "your secret key";
// ? ? ? String bucket = "haluki";
?
? ? ? ?//默認(rèn)不指定key的情況下,以文件內(nèi)容的hash值作為文件名
? ? ? ?String key = "2022/xxx.png";
?
? ? ? ?try {
// ? ? ? ? ? byte[] uploadBytes = "hello qiniu cloud".getBytes("utf-8");
// ? ? ? ? ? ByteArrayInputStream byteInputStream=new ByteArrayInputStream(uploadBytes);
?
?
? ? ? ? ? ?InputStream inputStream = new FileInputStream("C:\\Users\\root\\Desktop\\Snipaste_2022-02-28_22-48-37.png");
? ? ? ? ? ?Auth auth = Auth.create(accessKey, secretKey);
? ? ? ? ? ?String upToken = auth.uploadToken(bucket);
?
? ? ? ? ? ?try {
? ? ? ? ? ? ? ?Response response = uploadManager.put(inputStream,key,upToken,null, null);
? ? ? ? ? ? ? ?//解析上傳成功的結(jié)果
? ? ? ? ? ? ? ?DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
? ? ? ? ? ? ? ?System.out.println(putRet.key);
? ? ? ? ? ? ? ?System.out.println(putRet.hash);
? ? ? ? ? } catch (QiniuException ex) {
? ? ? ? ? ? ? ?Response r = ex.response;
? ? ? ? ? ? ? ?System.err.println(r.toString());
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?System.err.println(r.bodyString());
? ? ? ? ? ? ? } catch (QiniuException ex2) {
? ? ? ? ? ? ? ? ? ?//ignore
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? } catch (Exception ex) {
? ? ? ? ? ?//ignore
? ? ? }
?
? }
}
JPA
Java持久層API,將實(shí)體對象持久化到數(shù)據(jù)庫中。
博客
前后端分離。單賬戶,一個人發(fā)布博客,其他人可以登錄評論,可改頭像和個人信息,可評論??梢陨蟼饔焰湥@些用戶上傳的友鏈和發(fā)表的評論需要在后臺審核通過后才可以正常的上傳成功,后臺管理員可以對其修改或者刪除。
安全部分
設(shè)置允許跨域的路徑,請求方式,允許時間,cookie是否允許等。
安全部分主要是避免XSS和CSRF攻擊
XSS跨站腳本攻擊即用戶在輸入框內(nèi)輸入JavaScript代碼,此項(xiàng)目使用secure-only方式,只允許Https請求讀取發(fā)送請求時自動發(fā)送cookie
CSRF:登錄生成token為JSON對象,再返回給前端,之后每次客戶端與服務(wù)器交互的時候都會帶著token,服務(wù)器獲取用戶token解析獲取userId,在redis中找,再把這兩個token對比。
如何防止跨站請求偽造CSRF攻擊
SpringSecurity去防止CSRF攻擊的方式就是通過csrf_token。后端會生成一個csrf_token,前端發(fā)起請求的時候需要攜帶這個csrf_token,后端會有過濾器進(jìn)行校驗(yàn),如果沒有攜帶或者是偽造的就不允許訪問。SpringSecurity默認(rèn)啟用了CSRF防護(hù),要禁用CSRF防護(hù),可以在configure()方法中配置HttpSecurity:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
?
?@Override
?protected void configure(HttpSecurity http)throws Exception{
? ?http.csrf().disable()
? ? .authorizeRequests()
? ? .antMatchers("/admin/**").hasRole("ADMIN")
? ? .antMatchers("/user/**").hasRole("USER")
? ? .anyRequest().authenticated()
? ? .and()
? ? .formLogin()
? ? .and()
? ? .httpBasic();
}
}
如何在SpringSecurity中自定義認(rèn)證邏輯
需要實(shí)現(xiàn)AuthenticationProvider接口,并在configure() 方法中將自定義的AuthenticationProvider添加到AuthenticationManagerBuilder。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
? ?@Autowired
?private CutomAuthenticationProvider provider;
?
?@Override
?protected void configure(AuthenticationManagerBuilder auth){
? ?auth.authenticationProvider(provider);
}
}
如何在SpringSecurity中使用基于角色的控制訪問
所以我們在項(xiàng)目中只需要把當(dāng)前登錄用戶的權(quán)限信息也存入Authentication。
? 然后設(shè)置我們的資源所需要的權(quán)限即可。
SpringSecurity為我們提供了基于注解的權(quán)限控制方案,這也是我們項(xiàng)目中主要采用的方式。我們可以使用注解去指定訪問對應(yīng)的資源所需的權(quán)限。
? 但是要使用它我們需要先開啟相關(guān)配置。
@EnableGlobalMethodSecurity(prePostEnabled = true)
? 然后就可以使用對應(yīng)的注解。@PreAuthorize
@RestController
public class HelloController {
?
? ?@RequestMapping("/hello")
? ?@PreAuthorize("hasAuthority('test')")
? ?public String hello(){
? ? ? ?return "hello";
? }
}
可以在configure()中配置HttpSecurity
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
?
?@Override
?protected void configure(HttpSecurity http)throws Exception{
? ?http.authorizeRequests()
? ? .antMatchers("/admin/**").hasRole("ADMIN")
? ? .antMatchers("/user/**").hasRole("USER")
? ? .anyRequest().authenticated()
? ? .and()
? ? .formLogin()
? ? .and()
? ? .httpBasic();
}
}
自定義jwt認(rèn)證過濾器
這個過濾器會去獲取請求頭中的token,對token進(jìn)行解析取出其中的userid。
? 使用userid去redis中獲取對應(yīng)的LoginUser對象。
? 然后封裝Authentication對象存入SecurityContextHolder
把jwtAuthenticationTokenFilter添加到SpringSecurity的過濾器鏈中
? http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
? //允許跨域
? http.cors();
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
?
? ?@Autowired
? ?private RedisCache redisCache;
?
? ?@Override
? ?protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
? ? ? ?//獲取token
? ? ? ?String token = request.getHeader("token");
? ? ? ?if (!StringUtils.hasText(token)) {
? ? ? ? ? ?//放行
? ? ? ? ? ?filterChain.doFilter(request, response);
? ? ? ? ? ?return;
? ? ? }
? ? ? ?//解析token
? ? ? ?String userid;
? ? ? ?try {
? ? ? ? ? ?Claims claims = JwtUtil.parseJWT(token);
? ? ? ? ? ?userid = claims.getSubject();
? ? ? } catch (Exception e) {
? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ?throw new RuntimeException("token非法");
? ? ? }
? ? ? ?//從redis中獲取用戶信息
? ? ? ?String redisKey = "login:" + userid;
? ? ? ?LoginUser loginUser = redisCache.getCacheObject(redisKey);
? ? ? ?if(Objects.isNull(loginUser)){
? ? ? ? ? ?throw new RuntimeException("用戶未登錄");
? ? ? }
? ? ? ?//存入SecurityContextHolder
? ? ? ?//TODO 獲取權(quán)限信息封裝到Authentication中
? ? ? ?UsernamePasswordAuthenticationToken authenticationToken =
? ? ? ? ? ? ? ?new UsernamePasswordAuthenticationToken(loginUser,null,null);
? ? ? ?SecurityContextHolder.getContext().setAuthentication(authenticationToken);
? ? ? ?//放行
? ? ? ?filterChain.doFilter(request, response);
? }
}
自定義認(rèn)證成功處理器
自定義認(rèn)證失敗處理
如果是認(rèn)證過程中出現(xiàn)的異常會被封裝成AuthenticationException然后調(diào)用AuthenticationEntryPoint對象的方法去進(jìn)行異常處理。
? 如果是授權(quán)過程中出現(xiàn)的異常會被封裝成AccessDeniedException然后調(diào)用AccessDeniedHandler對象的方法去進(jìn)行異常處理。
所以,如果我們需要自定義異常處理,我們只需要自定義AuthenticationEntryPoint和AccessDeniedHandler然后配置給SpringSecurity即可
自定義實(shí)現(xiàn)類
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
? ?@Override
? ?public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
? ? ? ?ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "權(quán)限不足");
? ? ? ?String json = JSON.toJSONString(result);
? ? ? ?WebUtils.renderString(response,json);
?
? }
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
? ?@Override
? ?public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
? ? ? ?ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "認(rèn)證失敗請重新登錄");
? ? ? ?String json = JSON.toJSONString(result);
? ? ? ?WebUtils.renderString(response,json);
? }
}
配置給SpringSecurity
先注入對應(yīng)的處理器
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
?
@Autowired
private AccessDeniedHandler accessDeniedHandler;
然后我們可以使用HttpSecurity對象的方法去配置
http.exceptionHandling().authenticationEntryPoint(autheticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
如何在springSecurity中實(shí)現(xiàn)jwt認(rèn)證
首先引入依賴:jjwt 實(shí)現(xiàn)一個用于生成和解析jwt的工具類 創(chuàng)建自定義的AuthenticationFilter,用于從請求頭中提取jwt并進(jìn)行認(rèn)證。 在securityConfig類中配置HttpSecurity,將自定義的AuthenticationFilter添加到過濾鏈 @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
?
?@Autowired
?private JwtAuthenticationFilter jwtAuFilter;
?
?@Override
?protected void configure(HttpSecurity http)throws Exception{
? ?http.csrf().disable()
? ? .sessionManagement()
? ? .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
? ? .and()
? ? .addFilterBefore(jwtAuFilter,usernamePasswordAuthentication.class)
? ? .anyRequest().authenticated();
? ?//把token校驗(yàn)過濾器添加到過濾器鏈中
? ? ? ?http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
在上述代碼中,禁用了CSRF防護(hù)和會話管理,然后將自定義的JwtAuthenticationFilter添加到過濾器鏈。
認(rèn)證成功的話要生成一個jwt,放入響應(yīng)中返回。并且為了讓用戶下回請求時能通過jwt識別出具體的是哪個用戶,我們需要把用戶信息存入redis,可以把用戶id作為key,最后把token響應(yīng)給前端。
@Service
public class LoginServiceImpl implements LoginServcie {
?
? ?@Autowired
? ?private AuthenticationManager authenticationManager;
? ?@Autowired
? ?private RedisCache redisCache;
?
? ?@Override
? ?public ResponseResult login(User user) {
? ? ? ?UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
? ? ? ?Authentication authenticate = authenticationManager.authenticate(authenticationToken);
? ? ? ?if(Objects.isNull(authenticate)){
? ? ? ? ? ?throw new RuntimeException("用戶名或密碼錯誤");
? ? ? }
? ? ? ?//使用userid生成token
? ? ? ?LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
? ? ? ?String userId = loginUser.getUser().getId().toString();
? ? ? ?String jwt = JwtUtil.createJWT(userId);
? ? ? ?//authenticate存入redis
? ? ? ?redisCache.setCacheObject("login:"+userId,loginUser);
? ? ? ?//把token響應(yīng)給前端
? ? ? ?HashMap
? ? ? ?map.put("token",jwt);
? ? ? ?return new ResponseResult(200,"登陸成功",map);
? }
}
得物項(xiàng)目
Redis給鍵值加過期時間
減少redis空間占用以防止用戶惡意查詢,在得物項(xiàng)目中使用的是定時刪除
Redis三大刪除策略
定時刪除:設(shè)置過期時間 惰性刪除:每次從數(shù)據(jù)庫中取鍵值時判斷是否過期 定期刪除:每隔一段時間查一次數(shù)據(jù)庫,可以在redis.config文件中配置,隨機(jī)刪除過期鍵
分布式鎖
為了確保分布式鎖可用,至少要確保鎖的實(shí)現(xiàn)同時滿足以下四個條件:
互斥性。在任意時刻,只有一個客戶端能持有鎖。 不會發(fā)生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續(xù)其他客戶端能加鎖。 加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖解開,不能誤解鎖。 具有容錯性。只要大多數(shù)Redis節(jié)點(diǎn)正常運(yùn)行,客戶端就能夠獲取和釋放鎖。
如何解決商品超賣
我使用了Redisson的分布式鎖組件
RLock rlock = redisson.getLock(key);
獲取到鎖->開啟事務(wù)->釋放鎖
// 1.構(gòu)造redisson實(shí)現(xiàn)分布式鎖必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);
// 2.構(gòu)造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 3.獲取鎖對象實(shí)例(無法保證是按線程的順序獲取到)
RLock rLock = redissonClient.getLock(lockKey);
try {
? ?/**
? ? * 4.嘗試獲取鎖
? ? * waitTimeout 嘗試獲取鎖的最大等待時間,超過這個值,則認(rèn)為獲取鎖失敗
? ? * leaseTime ? 鎖的持有時間,超過這個時間鎖會自動失效(值應(yīng)設(shè)置為大于業(yè)務(wù)處理的時間,確保在鎖有效期內(nèi)業(yè)務(wù)能處理完)
? ? */
? ?boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
? ?if (res) {
? ? ? ?//成功獲得鎖,在這里處理業(yè)務(wù)
? }
} catch (Exception e) {
? ?throw new RuntimeException("aquire lock fail");
}finally{
? ?//無論如何, 最后都要解鎖
? ?rLock.unlock();
}
Redis針對緩存穿透、擊穿、雪崩的解決方案
緩存穿透:存在則緩存返回,不存在則查詢db,惡意查詢不存在的key對后端壓力增大就叫穿透 解決方案:查詢?yōu)榭盏臅r候緩存下(緩存空值,并設(shè)置過期時間),查詢以后更新緩存,過濾key 緩存擊穿:緩存中沒有但數(shù)據(jù)庫中也可能沒有,大量查詢穿過緩存直擊數(shù)據(jù)庫 解決方案:使用互斥鎖,當(dāng)1號線程查詢緩存未命中時,去獲取互斥鎖,然后查詢數(shù)據(jù)庫獲取結(jié)果并將結(jié)果寫入到緩存中,最后釋放鎖,在1號線程釋放鎖之前,其他線程都不能獲取到鎖,只能睡眠一段時間后重試,如果能命中緩存,則返回數(shù)據(jù),否則繼續(xù)嘗試獲取互斥鎖 緩存雪崩:緩存服務(wù)器重啟或者大量緩存失效,都去訪問數(shù)據(jù)庫,引起數(shù)據(jù)庫壓力過大 解決方案:將key的過期時間分布均勻,避免短時間內(nèi)大量key過期,加鎖避免過大并發(fā)量,控制訪問流量。
訂單支付模塊
用戶下單付款 客戶端請求服務(wù)器獲取簽名后的訂單信息。 返回簽名后的訂單信息 調(diào)用支付接口,發(fā)送支付請求 返回支付結(jié)果 返回給服務(wù)器驗(yàn)證簽名,解析支付結(jié)果 返回最終支付結(jié)果 顯示支付結(jié)果
調(diào)用支付寶SDK,實(shí)例化客戶端
new DefaultAliPayClient("網(wǎng)關(guān)地址",APPID,應(yīng)用公鑰,參數(shù)格式JSON,編碼格式UTF-8,支付寶公鑰,簽名方式,RSA2)
調(diào)用接口生成表單,把信息填進(jìn)去,orderID、金額等。下單成功后更新支付記錄,更新商品付款人數(shù),更新訂單狀態(tài)。
配置回調(diào)地址->調(diào)用支付接口->支付成功->跳轉(zhuǎn)到回調(diào)地址->判斷訂單狀態(tài)
狀態(tài)為已支付:更新訂單狀態(tài)->更新支付流水->更新付款人數(shù)
狀態(tài)為未支付:更新訂單狀態(tài)為未支付->更新支付流水為支付失敗
豆瓣項(xiàng)目
Redis支持
String:此類型是二進(jìn)制安全的,可包含任何數(shù)據(jù),如jpg圖片或序列化的對象
Hash:是一個鍵值對集合,適合用于存儲對象
List:字符串列表,可在頭部(左邊)或尾部(右邊)插入數(shù)據(jù)
Set:是String類型的無序集合
ZSet:同Set,并且不允許重復(fù)成員
Mongodb與Mysql的區(qū)別
Mongodb本質(zhì)上還是一個數(shù)據(jù)庫產(chǎn)品,與MySQL的區(qū)別在于它不會遵循一些約束,比如sql標(biāo)準(zhǔn)、表結(jié)構(gòu)等
Mongodb特性
面向集合文檔的存儲,適合存儲Bson(JSON的擴(kuò)展)形式的數(shù)據(jù) 格式自由 強(qiáng)大的查詢語句 完整的索引支持 支持二進(jìn)制數(shù)據(jù)以及大型對象(文件)的高效存儲
爬蟲是如何實(shí)現(xiàn)的
構(gòu)建請求頭,不同的爬取目標(biāo)有不同的值。
String getContent(String url,Map
? ?//定義request
Builder reqBuilder = new Request.Builder().url(url);
//如果傳入httpheader,則放入request中
if(headers!=null && !headers.isEmpty()){
? ? ? ?for(String key:headers.Keyset()){
? ? ? ? ?reqBuilder.addHeader(key,headers.get(key));
}
? }
?Request request ?= reqBuilder.build();
?//調(diào)用client去請求
?Call call = okHttpClient.newCall(reqeust);
?//返回結(jié)果字符串
?String result = null;
?try{
? ? ?//獲得返回結(jié)果
? logger.info("Requst" + url+ "Begin.");
? result = call.execute().body().string();
}catch(IOException e){
? ? ?logger.error("request"+url+"exception"+e);
}
?return result;
}
獲取數(shù)據(jù)后,轉(zhuǎn)換成Map格式
Map dataObj = JSON.parseObject(content,Map.class);
解析獲取到的數(shù)據(jù)
Map songListData = (Map)dataObj.get("songlist");
柚子快報激活碼778899分享:java 簡歷項(xiàng)目知識,八股文
推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。