प्रौद्योगिकी साझेदारी

SpringSecurity Framework [प्रमाणीकरणम्] ।

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

सामग्रीसूची

1. द्रुतप्रारम्भ

2. प्रमाणीकरणम्

२.१ प्रवेशसत्यापनप्रक्रिया

२.२ सिद्धान्तस्य प्रारम्भिक अन्वेषणम्

२.३ समस्यायाः समाधानं कुर्वन्तु

२.३.१ विचारविश्लेषणम्

२.३.२ सज्जता

२.३.३ कार्यान्वयनम्

२.३.३.१ दत्तांशकोशसत्यापनप्रयोक्ता

२.३.३.२ गुप्तशब्दगुप्तभण्डारणम्

२.३.३.३ प्रवेशान्तरफलकम्

२.३.३.४ प्रमाणीकरण-छिद्रम्

२.३.३.५ लॉग आउट्


वसन्तसुरक्षा वसन्तपरिवारे सुरक्षाप्रबन्धनरूपरेखा अस्ति, अन्यस्य सुरक्षारूपरेखायाः शिरो इत्यस्य तुलने, एतत् शिरो इत्यस्मात् समृद्धतरकार्यं समृद्धतरं सामुदायिकसंसाधनं च प्रदाति

सामान्यतया बृहत्परियोजनासु वसन्तसुरक्षायाः उपयोगः अधिकः भवति, लघुपरियोजनासु च शिरो इत्यस्य उपयोगः अधिकः भवति, यतः वसन्तसुरक्षायाः तुलने शिरो इत्यस्य आरम्भः सुकरः भवति

सामान्यजाल-अनुप्रयोगानाम् आवश्यकता अस्तिप्रमाणीकरणतथाअधिकृत्यताम्

  • प्रमाणीकरणम् : प्रणालीं अभिगच्छन् वर्तमानः उपयोक्ता प्रणालीप्रयोक्ता अस्ति वा इति सत्यापयन्तु, तथा च सः कः उपयोक्ता अस्ति इति पुष्टिं कुर्वन्तु ।
  • प्राधिकरणम् : प्रमाणीकरणानन्तरं वर्तमानप्रयोक्तुः कार्यं कर्तुं अनुमतिः अस्ति वा इति निर्धारयन्तु

प्रमाणीकरणं प्राधिकरणं च सुरक्षारूपरेखारूपेण Spring Security इत्यस्य मूलकार्यं भवति!

1. द्रुतप्रारम्भ

प्रथमं केवलं SpringBoot परियोजनां निर्मामः ।

अस्मिन् समये वयं निर्माणं सफलं वा इति सत्यापयितुं लिखितं सरलं नमस्कार-अन्तरफलकं प्राप्नुमः ।

ततः SpringSecurity इत्यस्य परिचयं कुर्वन्तु ।

अस्मिन् समये अभिगम-अन्तरफलकस्य प्रभावं पश्यामः ।

SpringSecurity इत्यस्य परिचयस्य अनन्तरं, अभिगमन-अन्तरफलकं स्वयमेव प्रवेश-पृष्ठं प्रति कूर्दति पूर्वनिर्धारितं उपयोक्तृनाम, तथा च गुप्तशब्दः कन्सोल्-मध्ये निर्गमः भविष्यति ।

2. प्रमाणीकरणम्

२.१ प्रवेशसत्यापनप्रक्रिया

सर्वप्रथमं अस्माभिः प्रवेशसत्यापनप्रक्रिया अवगन्तुं आवश्यकं प्रथमं, अग्रभागः प्रवेशान्तरफलकं प्राप्तुं उपयोक्तृनाम गुप्तशब्दं च वहति ततः परं सः तस्य तुलनां दत्तांशकोशे विद्यमानैः सह करोति यदि उपयोक्तृनाम/उपयोक्तृ-ID सम्यक् उपयुज्यते, तर्हि jwt उत्पद्यते, ततः jwt -इत्यस्य अग्रभागं प्रति प्रतिक्रियां ददातु, ततः प्रवेशानन्तरं अन्य-अनुरोधानाम् अभिगमनं प्रत्येकं समये सर्वरः टोकनं प्राप्नोति विश्लेषणार्थं अनुरोधशीर्षकं, UserID प्राप्नोति, तथा च उपयोक्तृनाम id इत्यस्य आधारेण उपयोक्तृसम्बद्धसूचनाः दर्शकस्य अनुमतिः च प्राप्नोति, यदि यदि भवतः अनुमतिः अस्ति तर्हि अग्रभागस्य प्रतिक्रियां ददातु

२.२ सिद्धान्तस्य प्रारम्भिक अन्वेषणम्

SpringSecurity इत्यस्य सिद्धान्तः वस्तुतः एकः फ़िल्टर-शृङ्खला अस्ति, यः विविध-कार्यैः सह फ़िल्टर्-प्रदानं करोति ।

  • UsernamePasswordAuthenticationFilter प्रवेशपृष्ठे उपयोक्तृनाम गुप्तशब्दं च पूरयित्वा प्रवेशानुरोधानाम् संसाधनार्थं उत्तरदायी भवति ।
  • ExceptionTranslationFilter फ़िल्टर श्रृङ्खलायां क्षिप्तं किमपि AccessDeniedException तथा AuthenticationException नियन्त्रयति
  • FilterSecurityInterceptor इति अनुमतिसत्यापनार्थं उत्तरदायी फ़िल्टरः अस्ति

वर्तमानप्रणाल्यां SpringSecurity फ़िल्टर श्रृङ्खलायां के के फ़िल्टराः सन्ति, तेषां क्रमः च द्रष्टुं वयं Debug इत्यस्य उपयोगं अपि कर्तुं शक्नुमः ।

तदनन्तरं प्रमाणीकरणप्रवाहचित्रस्य विश्लेषणं पश्यामः ।

अत्र अस्माकं केवलं प्रक्रियां अवगन्तुं शक्नुवन्तु इति सरलतया वक्तुं शक्यते ।

उपयोक्ता उपयोक्तृनाम गुप्तशब्दं च प्रदत्तवान्, UsernamePasswordAuthenticationFilter तत् Authentication ऑब्जेक्ट् इत्यस्य रूपेण समाहितं करोति, ततः प्रमाणीकरणार्थं DaoAuthenticationProvider इत्यस्य authenticate मेथड् आह्वयति, ततः उपयोक्तारं पृच्छितुं loadUserByUserName मेथड् आह्वयति स्मृतौ अन्वेषणं कर्तुं भवति, ततः तत्सम्बद्धं उपयोक्तृसूचनाम् UserDetails ऑब्जेक्ट् मध्ये Encapsulate कुर्वन्तु, UserDetails मध्ये गुप्तशब्दस्य तुलनां कर्तुं PasswordEncoder इत्यस्य उपयोगं कुर्वन्तु तथा च Authentication गुप्तशब्दस्य उपयोगं कुर्वन्तु यत् यदि सम्यक् अस्ति तर्हि UserDetails इत्यस्मिन् अनुमतिसूचना Authentication ऑब्जेक्ट् इत्यत्र सेट् कुर्वन्तु , ततः Authentication ऑब्जेक्ट् प्रत्यागच्छतु, अन्ते च SecurityContextHolder.getContext( ).setAuthentication मेथड् इत्यस्य उपयोगं कुर्वन्तु एतत् ऑब्जेक्ट् संगृह्णाति, अन्ये च फ़िल्टर् SecurityContextHoder इत्यस्य माध्यमेन वर्तमानं उपयोक्तृसूचनाम् प्राप्नुयुः (एतत् अवगन्तुं भवता एतत् अनुच्छेदं कण्ठस्थीकरणस्य आवश्यकता नास्ति)

ततः वयं प्रक्रियां परिवर्तयितुं शक्नुमः ततः पूर्वं प्रथमं स्मृत्याः अन्वेषणकाले अस्माभिः दत्तांशकोशात् अन्वेषणं करणीयम् (अत्र अस्माभिः UserDetailsService implementation class इत्यस्य अनुकूलनं करणीयम्), तथा च वयं default The user name and password इत्यस्य उपयोगं न करिष्यामः , तथा च प्रवेशान्तरफलकं स्वयमेव लिखितं भवितुमर्हति, तस्य प्रदत्तस्य पूर्वनिर्धारितप्रवेशपृष्ठस्य उपयोगस्य आवश्यकता नास्ति ।

अस्माभिः विश्लेषितस्य परिस्थितेः आधारेण वयं तादृशं चित्रं प्राप्तुं शक्नुमः।

अस्मिन् समये, एकः jwt अग्रे अन्ते प्रत्यागच्छति, तथा च अग्रे अन्तः कृताः अन्ये अनुरोधाः टोकनं वहन्ति अतः अस्माकं प्रथमं सोपानं टोकनं वहति वा इति परीक्षितुं, टोकनस्य विश्लेषणं कृत्वा, तत्सम्बद्धं userid प्राप्तुं, एनकैप्सुलेट् कर्तुं च अस्ति it as The Anthentication object SecurityContextHolder इत्यस्मिन् संगृहीतं भवति (यथा अन्ये फ़िल्टर्स् तत् प्राप्तुं शक्नुवन्ति) ।

अतः अत्र अन्यः प्रश्नः अस्ति यत् jwt प्रमाणीकरण-छिद्रात् userid प्राप्त्वा सम्पूर्ण-उपयोक्तृ-सूचना कथं प्राप्तव्या?

अत्र वयं redis redis तः userid मार्गेण।

२.३ समस्यायाः समाधानं कुर्वन्तु

२.३.१ विचारविश्लेषणम्

उपर्युक्तसिद्धान्तानां प्रारम्भिक अन्वेषणात् अस्माभिः अपि मोटेन विश्लेषितं यत् यदि वयं स्वयमेव अग्रे-अन्त-पृष्ठ-अन्त-पृथक्करणस्य प्रमाणीकरण-प्रक्रियाम् कार्यान्वयामः तर्हि अस्माभिः किं कर्तव्यम् इति

प्रवेशं कुर्वन्तु:

क. कस्टम लॉगिन अन्तरफलक

प्रमाणीकरणार्थं ProviderManager पद्धतिं आह्वयन्तु यदि प्रमाणीकरणं उत्तीर्णं भवति तर्हि jwt उत्पद्यते ।

उपयोक्तृसूचनाः redis इत्यत्र संग्रहयन्तु

ख. UserDetailsService इत्यस्य अनुकूलनं कुर्वन्तु

अस्मिन् कार्यान्वयनवर्गे दत्तांशकोशं पृच्छन्तु

अनुशीलय:

क. jwt प्रमाणीकरण-छिद्रं अनुकूलितं कुर्वन्तु

टोकन प्राप्त करें

तस्य userid प्राप्तुं टोकनस्य विश्लेषणं कुर्वन्तु

redis इत्यस्मात् सम्पूर्णं उपयोक्तृसूचनां प्राप्नुवन्तु

SecurityContextHolder इत्यत्र संग्रहणं कुर्वन्तु

२.३.२ सज्जता

प्रथमं भवद्भिः तत्सम्बद्धानि आश्रयाणि योजयितव्यानि

  1. <!-- SpringSecurity启动器 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>
  6. <!-- redis依赖 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-data-redis</artifactId>
  10. </dependency>
  11. <!-- fastjson依赖 -->
  12. <dependency>
  13. <groupId>com.alibaba</groupId>
  14. <artifactId>fastjson</artifactId>
  15. <version>1.2.33</version>
  16. </dependency>
  17. <!-- jwt依赖 -->
  18. <dependency>
  19. <groupId>io.jsonwebtoken</groupId>
  20. <artifactId>jjwt</artifactId>
  21. <version>0.9.0</version>
  22. </dependency>

ततः अस्माभिः Redis इत्यस्य उपयोगः करणीयः, Redis सम्बद्धानि विन्यासानि योजयितुं च आवश्यकम् ।

प्रथमं FastJson इत्यस्य सीरियलाइजर् अस्ति

  1. package org.example.utils;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.parser.ParserConfig;
  4. import com.alibaba.fastjson.serializer.SerializerFeature;
  5. import com.fasterxml.jackson.databind.JavaType;
  6. import com.fasterxml.jackson.databind.type.TypeFactory;
  7. import org.springframework.data.redis.serializer.RedisSerializer;
  8. import org.springframework.data.redis.serializer.SerializationException;
  9. import java.nio.charset.Charset;
  10. /**
  11. * Redis使用fastjson序列化
  12. * @param <T>
  13. */
  14. public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
  15. public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
  16. private Class<T> clazz;
  17. static {
  18. ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
  19. }
  20. public FastJsonRedisSerializer(Class<T> clazz){
  21. super();
  22. this.clazz=clazz;
  23. }
  24. @Override
  25. public byte[] serialize(T t) throws SerializationException {
  26. if (t == null){
  27. return new byte[0];
  28. }
  29. return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
  30. }
  31. @Override
  32. public T deserialize(byte[] bytes) throws SerializationException {
  33. if (bytes==null || bytes.length<=0){
  34. return null;
  35. }
  36. String str = new String(bytes,DEFAULT_CHARSET);
  37. return JSON.parseObject(str,clazz);
  38. }
  39. protected JavaType getJavaType(Class<?> clazz){
  40. return TypeFactory.defaultInstance().constructType(clazz);
  41. }
  42. }

RedisConfig रचयन्तु तस्मिन् च विकृतवर्णादिसमस्यानां समाधानार्थं क्रमाङ्ककं रचयन्तु ।

  1. package org.example.config;
  2. import org.example.utils.FastJsonRedisSerializer;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.data.redis.connection.RedisConnectionFactory;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.data.redis.serializer.StringRedisSerializer;
  8. @Configuration
  9. public class RedisConfig {
  10. @Bean
  11. @SuppressWarnings(value = {"unchecked","rawtypes"})
  12. public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
  13. RedisTemplate<Object,Object> template = new RedisTemplate<>();
  14. template.setConnectionFactory(connectionFactory);
  15. FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
  16. //使用StringRedisSerializer来序列化和反序列化redus的key值
  17. template.setKeySerializer(new StringRedisSerializer());
  18. template.setValueSerializer(serializer);
  19. template.afterPropertiesSet();
  20. return template;
  21. }
  22. }

प्रतिक्रियावर्गस्य एकीकरणं अपि आवश्यकम् अस्ति

  1. package org.example.domain;
  2. import com.fasterxml.jackson.annotation.JsonInclude;
  3. @JsonInclude(JsonInclude.Include.NON_NULL)
  4. public class ResponseResult<T>{
  5. /**
  6. * 状态码
  7. */
  8. private Integer code;
  9. /**
  10. * 提示信息,如果有错误时,前端可以获取该字段进行提示
  11. */
  12. private String msg;
  13. /**
  14. * 查询到的结果数据
  15. */
  16. private T data;
  17. public ResponseResult(Integer code,String msg){
  18. this.code = code;
  19. this.msg = msg;
  20. }
  21. public ResponseResult(Integer code,T data){
  22. this.code = code;
  23. this.data = data;
  24. }
  25. public Integer getCode() {
  26. return code;
  27. }
  28. public void setCode(Integer code) {
  29. this.code = code;
  30. }
  31. public String getMsg() {
  32. return msg;
  33. }
  34. public void setMsg(String msg) {
  35. this.msg = msg;
  36. }
  37. public T getData() {
  38. return data;
  39. }
  40. public void setData(T data) {
  41. this.data = data;
  42. }
  43. public ResponseResult(Integer code,String msg,T data){
  44. this.code = code;
  45. this.msg = msg;
  46. this.data = data;
  47. }
  48. }

jwt जनयितुं jwt इत्यस्य विश्लेषणं कर्तुं च भवतः jwt tool classes इत्यस्य आवश्यकता अस्ति ।

  1. package org.example.utils;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.JwtBuilder;
  4. import io.jsonwebtoken.Jwts;
  5. import io.jsonwebtoken.SignatureAlgorithm;
  6. import javax.crypto.SecretKey;
  7. import javax.crypto.spec.SecretKeySpec;
  8. import java.util.Base64;
  9. import java.util.Date;
  10. import java.util.UUID;
  11. public class JwtUtil {
  12. //有效期为
  13. public static final Long JWT_TTL = 60*60*1000L; //一个小时
  14. //设置密钥明文
  15. public static final String JWT_KEY = "hzj";
  16. public static String getUUID(){
  17. String token = UUID.randomUUID().toString().replaceAll("-","");
  18. return token;
  19. }
  20. /**
  21. * 生成jwt
  22. * @param subject token中要存放的数据(json格式)
  23. * @return
  24. */
  25. public static String createJWT(String subject){
  26. JwtBuilder builder = getJwtBuilder(subject,null,getUUID()); //设置过期时间
  27. return builder.compact();
  28. }
  29. /**
  30. * 生成jwt
  31. * @param subject token中要存放的数据(json格式)
  32. * @param ttlMillis token超时时间
  33. * @return
  34. */
  35. public static String createJWT(String subject,Long ttlMillis){
  36. JwtBuilder builder = getJwtBuilder(subject,ttlMillis,getUUID()); //设置过期时间
  37. return builder.compact();
  38. }
  39. private static JwtBuilder getJwtBuilder(String subject,Long ttlMillis,String uuid){
  40. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
  41. SecretKey secretKey = generalkey();
  42. long nowMillis = System.currentTimeMillis();
  43. Date now = new Date(nowMillis);
  44. if(ttlMillis==null){
  45. ttlMillis=JwtUtil.JWT_TTL;
  46. }
  47. long expMillis = nowMillis + ttlMillis;
  48. Date expDate = new Date(expMillis);
  49. return Jwts.builder()
  50. .setId(uuid) //唯一的Id
  51. .setSubject(subject) //主题 可以是Json数据
  52. .setIssuer("hzj") //签发者
  53. .setIssuedAt(now) //签发时间
  54. .signWith(signatureAlgorithm,secretKey) //使用HS256对称加密算法签名,第二个参数为密钥
  55. .setExpiration(expDate);
  56. }
  57. /**
  58. * 创建token
  59. * @param id
  60. * @param subject
  61. * @param ttlMillis
  62. * @return
  63. */
  64. public static String createJWT(String id,String subject,Long ttlMillis){
  65. JwtBuilder builder = getJwtBuilder(subject,ttlMillis,id);//设置过期时间
  66. return builder.compact();
  67. }
  68. public static void main(String[] args) throws Exception{
  69. String token =
  70. "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTg0MjU5MzIsInVzZX" +
  71. "JJZCI6MTExLCJ1c2VybmFtZSI6Ik1hcmtaUVAifQ.PTlOdRG7ROVJqPrA0q2ac7rKFzNNFR3lTMyP_8fIw9Q";
  72. Claims claims = parseJWT(token);
  73. System.out.println(claims);
  74. }
  75. /**
  76. * 生成加密后的密钥secretkey
  77. * @return
  78. */
  79. public static SecretKey generalkey(){
  80. byte[] encodeedkey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
  81. SecretKey key = new SecretKeySpec(encodeedkey,0,encodeedkey.length,"AES");
  82. return key;
  83. }
  84. /**
  85. * 解析
  86. * @param jwt
  87. * @return
  88. * @throws Exception
  89. */
  90. public static Claims parseJWT(String jwt) throws Exception{
  91. SecretKey secretKey = generalkey();
  92. return Jwts.parser()
  93. .setSigningKey(secretKey)
  94. .parseClaimsJws(jwt)
  95. .getBody();
  96. }
  97. }

अन्यं Redis tool class RedisCache परिभाषयन्तु, यत् अस्माकं कृते redistemplate इत्यस्य आह्वानं सुलभं करोति

  1. package org.example.utils;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.BoundSetOperations;
  4. import org.springframework.data.redis.core.HashOperations;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.data.redis.core.ValueOperations;
  7. import org.springframework.stereotype.Component;
  8. import java.util.*;
  9. import java.util.concurrent.TimeUnit;
  10. @SuppressWarnings(value = { "unchecked", "rawtypes" })
  11. @Component
  12. public class RedisCache
  13. {
  14. @Autowired
  15. public RedisTemplate redisTemplate;
  16. /**
  17. * 缓存基本的对象,Integer、String、实体类等
  18. *
  19. * @param key 缓存的键值
  20. * @param value 缓存的值
  21. */
  22. public <T> void setCacheObject(final String key, final T value)
  23. {
  24. redisTemplate.opsForValue().set(key, value);
  25. }
  26. /**
  27. * 缓存基本的对象,Integer、String、实体类等
  28. *
  29. * @param key 缓存的键值
  30. * @param value 缓存的值
  31. * @param timeout 时间
  32. * @param timeUnit 时间颗粒度
  33. */
  34. public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
  35. {
  36. redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
  37. }
  38. /**
  39. * 设置有效时间
  40. *
  41. * @param key Redis键
  42. * @param timeout 超时时间
  43. * @return true=设置成功;false=设置失败
  44. */
  45. public boolean expire(final String key, final long timeout)
  46. {
  47. return expire(key, timeout, TimeUnit.SECONDS);
  48. }
  49. /**
  50. * 设置有效时间
  51. *
  52. * @param key Redis键
  53. * @param timeout 超时时间
  54. * @param unit 时间单位
  55. * @return true=设置成功;false=设置失败
  56. */
  57. public boolean expire(final String key, final long timeout, final TimeUnit unit)
  58. {
  59. return redisTemplate.expire(key, timeout, unit);
  60. }
  61. /**
  62. * 获得缓存的基本对象。
  63. *
  64. * @param key 缓存键值
  65. * @return 缓存键值对应的数据
  66. */
  67. public <T> T getCacheObject(final String key)
  68. {
  69. ValueOperations<String, T> operation = redisTemplate.opsForValue();
  70. return operation.get(key);
  71. }
  72. /**
  73. * 删除单个对象
  74. *
  75. * @param key
  76. */
  77. public boolean deleteObject(final String key)
  78. {
  79. return redisTemplate.delete(key);
  80. }
  81. /**
  82. * 删除集合对象
  83. *
  84. * @param collection 多个对象
  85. * @return
  86. */
  87. public long deleteObject(final Collection collection)
  88. {
  89. return redisTemplate.delete(collection);
  90. }
  91. /**
  92. * 缓存List数据
  93. *
  94. * @param key 缓存的键值
  95. * @param dataList 待缓存的List数据
  96. * @return 缓存的对象
  97. */
  98. public <T> long setCacheList(final String key, final List<T> dataList)
  99. {
  100. Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
  101. return count == null ? 0 : count;
  102. }
  103. /**
  104. * 获得缓存的list对象
  105. *
  106. * @param key 缓存的键值
  107. * @return 缓存键值对应的数据
  108. */
  109. public <T> List<T> getCacheList(final String key)
  110. {
  111. return redisTemplate.opsForList().range(key, 0, -1);
  112. }
  113. /**
  114. * 缓存Set
  115. *
  116. * @param key 缓存键值
  117. * @param dataSet 缓存的数据
  118. * @return 缓存数据的对象
  119. */
  120. public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
  121. {
  122. BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
  123. Iterator<T> it = dataSet.iterator();
  124. while (it.hasNext())
  125. {
  126. setOperation.add(it.next());
  127. }
  128. return setOperation;
  129. }
  130. /**
  131. * 获得缓存的set
  132. *
  133. * @param key
  134. * @return
  135. */
  136. public <T> Set<T> getCacheSet(final String key)
  137. {
  138. return redisTemplate.opsForSet().members(key);
  139. }
  140. /**
  141. * 缓存Map
  142. *
  143. * @param key
  144. * @param dataMap
  145. */
  146. public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
  147. {
  148. if (dataMap != null) {
  149. redisTemplate.opsForHash().putAll(key, dataMap);
  150. }
  151. }
  152. /**
  153. * 获得缓存的Map
  154. *
  155. * @param key
  156. * @return
  157. */
  158. public <T> Map<String, T> getCacheMap(final String key)
  159. {
  160. return redisTemplate.opsForHash().entries(key);
  161. }
  162. /**
  163. * 往Hash中存入数据
  164. *
  165. * @param key Redis键
  166. * @param hKey Hash键
  167. * @param value 值
  168. */
  169. public <T> void setCacheMapValue(final String key, final String hKey, final T value)
  170. {
  171. redisTemplate.opsForHash().put(key, hKey, value);
  172. }
  173. /**
  174. * 获取Hash中的数据
  175. *
  176. * @param key Redis键
  177. * @param hKey Hash键
  178. * @return Hash中的对象
  179. */
  180. public <T> T getCacheMapValue(final String key, final String hKey)
  181. {
  182. HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
  183. return opsForHash.get(key, hKey);
  184. }
  185. /**
  186. * 删除Hash中的数据
  187. *
  188. * @param key
  189. * @param hkey
  190. */
  191. public void delCacheMapValue(final String key, final String hkey)
  192. {
  193. HashOperations hashOperations = redisTemplate.opsForHash();
  194. hashOperations.delete(key, hkey);
  195. }
  196. /**
  197. * 获取多个Hash中的数据
  198. *
  199. * @param key Redis键
  200. * @param hKeys Hash键集合
  201. * @return Hash对象集合
  202. */
  203. public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
  204. {
  205. return redisTemplate.opsForHash().multiGet(key, hKeys);
  206. }
  207. /**
  208. * 获得缓存的基本对象列表
  209. *
  210. * @param pattern 字符串前缀
  211. * @return 对象列表
  212. */
  213. public Collection<String> keys(final String pattern)
  214. {
  215. return redisTemplate.keys(pattern);
  216. }
  217. }

वयं प्रतिक्रियायाः कृते data अपि लिखितुं शक्नुमः, अतः अस्माकं कृते WebUtils इति टूल् क्लास् अपि आवश्यकम् अस्ति

  1. package org.example.utils;
  2. import javax.servlet.http.HttpServletResponse;
  3. import java.io.IOException;
  4. public class WebUtils {
  5. /**
  6. * 将字符串渲染到客户端
  7. *
  8. * @param response 渲染对象
  9. * @param string 待渲染的字符串
  10. * @return null
  11. */
  12. public static String renderString(HttpServletResponse response, String string) {
  13. try
  14. {
  15. response.setStatus(200);
  16. response.setContentType("application/json");
  17. response.setCharacterEncoding("utf-8");
  18. response.getWriter().print(string);
  19. }
  20. catch (IOException e)
  21. {
  22. e.printStackTrace();
  23. }
  24. return null;
  25. }
  26. }

अन्ते तत्सम्बद्धं उपयोक्तृसत्तावर्गं लिखन्तु

  1. package org.example.domain;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import java.io.Serializable;
  6. import java.util.Date;
  7. /**
  8. * 用户表(User)实体类
  9. */
  10. @Data
  11. @AllArgsConstructor
  12. @NoArgsConstructor
  13. public class User implements Serializable {
  14. private static final long serialVersionUID = -40356785423868312L;
  15. /**
  16. * 主键
  17. */
  18. private Long id;
  19. /**
  20. * 用户名
  21. */
  22. private String userName;
  23. /**
  24. * 昵称
  25. */
  26. private String nickName;
  27. /**
  28. * 密码
  29. */
  30. private String password;
  31. /**
  32. * 账号状态(0正常 1停用)
  33. */
  34. private String status;
  35. /**
  36. * 邮箱
  37. */
  38. private String email;
  39. /**
  40. * 手机号
  41. */
  42. private String phonenumber;
  43. /**
  44. * 用户性别(0男,1女,2未知)
  45. */
  46. private String sex;
  47. /**
  48. * 头像
  49. */
  50. private String avatar;
  51. /**
  52. * 用户类型(0管理员,1普通用户)
  53. */
  54. private String userType;
  55. /**
  56. * 创建人的用户id
  57. */
  58. private Long createBy;
  59. /**
  60. * 创建时间
  61. */
  62. private Date createTime;
  63. /**
  64. * 更新人
  65. */
  66. private Long updateBy;
  67. /**
  68. * 更新时间
  69. */
  70. private Date updateTime;
  71. /**
  72. * 删除标志(0代表未删除,1代表已删除)
  73. */
  74. private Integer delFlag;
  75. }

उपरि अस्माकं विश्लेषणस्य अनुसारं, SpringSecuriry इत्यस्य अस्माकं UserDetailsService इत्यस्य उपयोगं कर्तुं अस्माकं UserDetailsService इत्यस्य अनुकूलनं करणीयम् । अस्माकं स्वकीयः UserDetailsService दत्तांशकोशात् उपयोक्तृनाम गुप्तशब्दं च पृच्छितुं शक्नोति ।

वयं प्रथमं sys_user इति database table रचयामः ।

  1. CREATE TABLE `sys_user` (
  2. `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
  4. `nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '呢称',
  5. `password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
  6. `status` char(1) DEFAULT '0' COMMENT '账号状态(0正常1停用)',
  7. `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
  8. `phonenumber` varchar(32) DEFAULT NULL COMMENT '手机号',
  9. `sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
  10. `avatar` varchar(128) DEFAULT NULL COMMENT '头像',
  11. `user_type` char(1) NOT NULL DEFAULT '1' COMMENT '用户类型(O管理员,1普通用户)',
  12. `create_by` bigint DEFAULT NULL COMMENT '创建人的用户id',
  13. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  14. `update_by` bigint DEFAULT NULL COMMENT '更新人',
  15. `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  16. `del_flag` int DEFAULT '0' COMMENT '删除标志(O代表未删除,1代表已删除)',
  17. PRIMARY KEY (`id`)
  18. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

ततः myBatisPlus तथा mysql चालकानां परिचयं कुर्वन्तु ।

  1. <dependency>
  2. <groupId>com.baomidou</groupId>
  3. <artifactId>mybatis-plus-boot-starter</artifactId>
  4. <version>3.4.3</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. </dependency>

ततः दत्तांशकोशस्य प्रासंगिकसूचनाः विन्यस्यताम् ।

ततः mapper interface UserMapper परिभाषयन्तु तथा च तत्सम्बद्धानि एनोटेशन्स् योजयितुं mybatisplus इत्यस्य उपयोगं कुर्वन्तु ।

  1. package org.example.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import org.example.domain.User;
  4. public interface UserMapper extends BaseMapper<User> {
  5. }

ततः घटकस्कैनिङ्गं विन्यस्यताम्

अन्ते mp इत्यस्य सामान्यरूपेण उपयोगः कर्तुं शक्यते वा इति परीक्ष्यताम् ।

जुनिट् परिचयं ददातु

एवं सामान्यतया तस्य उपयोगः कर्तुं शक्यते ।

२.३.३ कार्यान्वयनम्

२.३.३.१ दत्तांशकोशसत्यापनप्रयोक्ता

तदनन्तरं अस्माभिः कोर कोड् कार्यान्वयनीयम् ।

प्रथमं UserDetailsService इत्यस्य अनुकूलनं कुर्मः ।

  1. package org.example.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import org.example.domain.LoginUser;
  4. import org.example.domain.User;
  5. import org.example.mapper.UserMapper;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.security.core.userdetails.UserDetailsService;
  9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  10. import org.springframework.stereotype.Service;
  11. import java.util.Objects;
  12. @Service
  13. public class UserDetailsServiceImpl implements UserDetailsService {
  14. @Autowired
  15. private UserMapper userMapper;
  16. @Override
  17. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  18. //查询用户信息 [InMemoryUserDetailsManager是在内存中查找]
  19. LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  20. wrapper.eq(User::getUserName,username);
  21. User user = userMapper.selectOne(wrapper);
  22. //如果查询不到数据就抛出异常,给出提示
  23. if(Objects.isNull(user)){
  24. throw new RuntimeException("用户名或密码错误!");
  25. }
  26. //TODO 查询权限信息
  27. //封装为UserDetails对象返回
  28. return new LoginUser(user);
  29. }
  30. }

अत्र user UserDetails इति रूपेण encapsulated भवति, प्रत्यागच्छति च ।

  1. package org.example.domain;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import org.springframework.security.core.userdetails.UserDetails;
  7. import java.util.Collection;
  8. @Data
  9. @AllArgsConstructor
  10. @NoArgsConstructor
  11. public class LoginUser implements UserDetails {
  12. private User user;
  13. @Override
  14. public Collection<? extends GrantedAuthority> getAuthorities() {
  15. return null;
  16. }
  17. @Override
  18. public String getPassword() {
  19. return user.getPassword();
  20. }
  21. @Override
  22. public String getUsername() {
  23. return user.getUserName();
  24. }
  25. @Override
  26. public boolean isAccountNonExpired() {
  27. return true;
  28. }
  29. @Override
  30. public boolean isAccountNonLocked() {
  31. return true;
  32. }
  33. @Override
  34. public boolean isCredentialsNonExpired() {
  35. return true;
  36. }
  37. @Override
  38. public boolean isEnabled() {
  39. return true;
  40. }
  41. }

अन्ते अत्र एकः बिन्दुः अस्ति, अर्थात् अस्माभिः दत्तांशकोशात् दत्तांशं प्राप्तुं प्रवेशस्य परीक्षणं कर्तव्यम्, अस्माभिः उपयोक्तुः दत्तांशः सारणीयां लिखितव्यः, यदि भवान् इच्छति यत् उपयोक्तुः गुप्तशब्दः स्पष्टपाठे प्रसारितः भवेत् , भवद्भिः गुप्तशब्दात् पूर्वं {noop} योजयितव्यम् ।

अत्र भवन्तः प्रवेशार्थं दत्तांशकोशे उपयोक्तृनाम गुप्तशब्दं च प्रविष्टुं शक्नुवन्ति ।

२.३.३.२ गुप्तशब्दगुप्तभण्डारणम्

कूटशब्दस्य पुरतः {noop} किमर्थं योजितः इति वदामः, यतः पूर्वनिर्धारित PasswordEncoder इत्यस्य आवश्यकता अस्ति यत् दत्तांशकोशे गुप्तशब्दस्य प्रारूपं {id}गुप्तशब्दः भवितुम् अर्हति एतत् id इत्यस्य आधारेण गुप्तशब्दस्य एन्क्रिप्शनविधिं निर्धारयिष्यति, परन्तु वयं सामान्यतया एतत् पद्धतिं न स्वीकुर्वन्तु, अतः PasswordEncoder इत्यस्य स्थाने अन्यं स्थापनं आवश्यकम् ।

तदनन्तरं वयं तस्य परीक्षणं कृत्वा पश्यामः।

भवान् द्रष्टुं शक्नोति यत् अस्माभिः अत्र पारितौ मूलगुप्तशब्दौ समानौ स्तः, परन्तु अस्माभिः भिन्नानि परिणामानि प्राप्तानि एतत् वस्तुतः लवणस्य अल्गोरिदम् इत्यनेन सह सम्बद्धम् अस्ति अहं पश्चात् कस्टम् एन्क्रिप्शनविषये लेखम् अपि लिखिष्यामि ।

एन्क्रिप्टेड् गुप्तशब्दं प्राप्त्वा भवन्तः एन्क्रिप्टेड् गुप्तशब्दं दत्तांशकोशे संग्रहीतुं शक्नुवन्ति ततः भवन्तः दत्तांशकोशे एन्क्रिप्टेड् गुप्तशब्देन सह अग्रभागात् पारितं साधारणपाठगुप्तशब्दं सत्याप्य प्रवेशं कर्तुं शक्नुवन्ति ।

अस्मिन् समये वयं परियोजनां प्रविष्टुं आरब्धाः, अपि च पूर्वगुप्तशब्देन प्रवेशं कर्तुं न शक्नुमः इति ज्ञातवन्तः, यतः दत्तांशकोशे पञ्जीकरणचरणस्य समये दत्तांशकोशे संगृहीतं गुप्तगुप्तशब्दं संग्रहीतुं अर्हति, न तु मूलगुप्तशब्दं (यतोहि अहं पञ्जीकरणं न कृतवान्, अहं गुप्तशब्दं गुप्तीकरणं करिष्यामि) गुप्तशब्दः स्वयमेव दत्तांशकोशे लिखितः भवति)।

२.३.३.३ प्रवेशान्तरफलकम्

अस्माभिः एकं प्रवेश-अन्तरफलकं कार्यान्वितुं आवश्यकं ततः SpringSecuruty इत्यनेन तस्य अनुमतिः दत्ता यदि तस्य अनुमतिः नास्ति तर्हि अन्तरफलके, उपयोक्तृ-प्रमाणीकरणं AuthenticationManager इत्यस्य प्रमाणीकरण-विधिना क्रियते, अतः अस्माभिः AuthenticationManager इत्यस्य विन्यस्तं करणीयम् SecurityConfig इत्यस्मिन् पात्रे ।

यदि प्रमाणीकरणं सफलं भवति तर्हि jwt उत्पन्नं कृत्वा प्रतिक्रियायां स्थापयितुं आवश्यकं यत् उपयोक्ता अग्रिमानुरोधं कुर्वन् jwt मार्गेण विशिष्टं उपयोक्तारं चिन्तयितुं शक्नोति, उपयोक्तृसूचना redis इत्यत्र संग्रहीतुं आवश्यकं भवति, उपयोक्तारं च ID इत्यस्य उपयोगः कीलरूपेण कर्तुं शक्यते ।

प्रथमं LoginController इति लिखन्तु

ततः तत्सम्बद्धं सेवां लिखत।

SecurityConfig मध्ये AuthenticationManager इत्येतत् इन्जेक्ट् कुर्वन्तु तथा च लॉगिन् इन्टरफेस् विमोचयन्तु ।

सेवायां व्यावसायिकतर्कस्य मध्ये यदि प्रमाणीकरणं विफलं भवति तर्हि कस्टम् अपवादः प्रत्यागमिष्यति, परन्तु यदि प्रमाणीकरणं सफलं भवति तर्हि तत्सम्बद्धां सूचनां कथं प्राप्नुमः

अत्र वयं debug कृत्वा प्राप्तानि ऑब्जेक्ट्स् द्रष्टुं शक्नुमः ।

अत्र ज्ञायते यत् तदनुरूपाः आवश्यकाः सूचनाः प्राचार्ये प्राप्तुं शक्यन्ते ।

ततः कोडं सम्पूर्णं कुर्वन्तु।

अन्ते तस्य परीक्षणं कुर्वन्तु।

२.३.३.४ प्रमाणीकरण-छिद्रम्

अहं प्रथमं कोडं चिनोमि।

  1. @Component
  2. public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  3. @Autowired
  4. private RedisCache redisCache;
  5. @Override
  6. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  7. //获取token
  8. String token = request.getHeader("token");
  9. if (!StringUtils.hasText(token)) {
  10. //放行
  11. filterChain.doFilter(request, response); //这里放行是因为还有后续的过滤器会给出对应的异常
  12. return; //token为空 不执行后续流程
  13. }
  14. //解析token
  15. String userid;
  16. try {
  17. Claims claims = JwtUtil.parseJWT(token);
  18. userid = claims.getSubject();
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. throw new RuntimeException("token非法!");
  22. }
  23. //从redis中获取用户信息
  24. String redisKey = "login:" + userid;
  25. LoginUser loginUser = redisCache.getCacheObject(redisKey);
  26. if (Objects.isNull(loginUser)){
  27. throw new RuntimeException("用户未登录!");
  28. }
  29. //将信息存入SecurityContextHolder(因为过滤器链后面的filter都是从中获取认证信息进行对应放行)
  30. //TODO 获取权限信息封装到Authentication中
  31. UsernamePasswordAuthenticationToken authenticationToken =
  32. new UsernamePasswordAuthenticationToken(loginUser,null,null);
  33. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  34. //放行
  35. filterChain.doFilter(request,response); //此时的放行是携带认证的,不同于上方token为空的放行
  36. }
  37. }

प्रथमं अत्र टोकन प्राप्तुं वयं अनुरोधशीर्षकात् तत्सम्बद्धं टोकनं प्राप्नुमः, ततः रिक्तः अस्ति इति परीक्षयामः यदि तत् रिक्तं भवति तर्हि अनुवर्तनप्रक्रियायाः माध्यमेन न गत्वा प्रत्यक्षतया मुक्तं करिष्यामः टोकनं विश्लेषयिष्यति, userid अन्तः प्राप्स्यति, ततः redis तः तत्सम्बद्धं उपयोक्तृसूचनां प्राप्नुत इति आधारेण userid इत्यस्य उपयोगं करिष्यति, अन्ते च SecurityContextHolder मध्ये संग्रहयिष्यति, यतः अनन्तरं फ़िल्टर्भ्यः तस्मात् दैनिकप्रमाणीकरणसूचना प्राप्तुं आवश्यकं भवति, अन्ते च प्रदर्शनं करणीयम् विश्लेषण संचालन।

अन्यः बिन्दुः यस्य ध्यानस्य आवश्यकता वर्तते सः अस्ति यत् SecurityContextHolder.getContext().setAuthentication() इत्यस्य प्रमाणीकरण-वस्तुमध्ये पारितस्य आवश्यकता वर्तते यदा वयं वस्तु निर्मामः तदा वयं त्रीणि पैरामीटर्स् उपयुञ्ज्महे, यतः तृतीयः पैरामीटर् प्रमाणीकरणं कर्तव्यं वा न वा इति निर्धारयितुं कुञ्जी भवति

तदनन्तरं अस्माभिः एतत् फ़िल्टर विन्यस्तं कर्तव्यम् ।

ततः यदा वयं उपयोक्तृ/प्रवेश-अन्तरफलकं प्राप्नुमः तदा टोकन-युक्तं प्रतिक्रिया-शरीरं अस्मान् प्रति प्रत्यागमिष्यति यदा पुनः नमस्कार-अन्तरफलकं प्राप्नुमः तदा तत् 403 भविष्यति यतः एतत् टोकनं न वहति, तस्मात् उपरिष्टात् कोड्-सङ्गतम् टोकन विना प्रतिक्रियाशरीरं मुक्तं भविष्यति तथा च प्रत्यागमनं अनन्तरं प्रक्रियां न निष्पादयिष्यति (अत्र विमोचनं यतोहि अन्ये छानकाः सन्ति ये विशेषतया पश्चात् संसाधनार्थं अपवादं क्षिपन्ति, प्रत्यागमनं च प्रतिक्रियाद्वारा गन्तुं निवारयितुं भवति प्रक्रिया)

अस्मिन् समये यदि वयं user/login द्वारा उत्पन्नं टोकनं hello interface इत्यस्य request header मध्ये स्थापयामः तर्हि सामान्यतया तत् अभिगन्तुं शक्नुमः ।

ततः अस्माकं फ़िल्टर्-समूहस्य उद्देश्यं प्राप्तम् (टोकन-प्राप्तिः, टोकन-पार्स्-करणं, SecurityContextHolder-मध्ये संग्रहणं च) ।

२.३.३.५ लॉग आउट्

अस्मिन् समये अस्माकं कृते केवलं redis मध्ये तत्सम्बद्धं दत्तांशं विलोपयितुं आवश्यकं यदा वयं पश्चात् अभिगमनाय टोकनम् आनयामः तदा redis मध्ये तत्सम्बद्धा उपयोक्तृसूचना अस्माकं कस्टम् फ़िल्टर मध्ये प्राप्ता भविष्यति भवन्तः तत् प्राप्तुं न शक्नुवन्ति, तस्य अर्थः अस्ति यत् भवन्तः प्रवेशं न कृतवन्तः।

/user/logout अन्तरफलकं प्राप्तुं वयं एतत् टोकनं वहामः ।

ततः logout function कार्यान्वितं भवति ।

अयं लेखः स्टेशन ख इत्यस्य तृतीयस्य अद्यतनात् ज्ञातः! ! !