Κοινή χρήση τεχνολογίας

SpringSecurity Framework [Έλεγχος ταυτότητας]

2024-07-12

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

Πίνακας περιεχομένων

1. Γρήγορη εκκίνηση

2. Πιστοποίηση

2.1 Διαδικασία επαλήθευσης σύνδεσης

2.2 Προκαταρκτική διερεύνηση της αρχής

2.3 Λύστε το πρόβλημα

2.3.1 Ανάλυση ιδεών

2.3.2 Προετοιμασία

2.3.3 Εφαρμογή

2.3.3.1 Χρήστης επαλήθευσης βάσης δεδομένων

2.3.3.2 Αποθήκευση κρυπτογραφημένη με κωδικό πρόσβασης

2.3.3.3 Διεπαφή σύνδεσης

2.3.3.4 Φίλτρο ελέγχου ταυτότητας

2.3.3.5 Αποσύνδεση


Το Spring Security είναι ένα πλαίσιο διαχείρισης ασφάλειας στην οικογένεια Spring Σε σύγκριση με το Shiro, ένα άλλο πλαίσιο ασφαλείας, παρέχει πλουσιότερες λειτουργίες και πλουσιότερους πόρους κοινότητας από το Shiro.

Σε γενικές γραμμές, το Spring Security χρησιμοποιείται πιο συχνά σε έργα μεγάλης κλίμακας και το Shiro χρησιμοποιείται πιο συχνά σε μικρά έργα, επειδή σε σύγκριση με το Spring Security, το Shiro είναι πιο εύκολο να ξεκινήσετε.

Οι γενικές εφαρμογές web πρέπει ναΠιστοποίησηκαιΕξουσιοδοτώ

  • Έλεγχος ταυτότητας: Επαληθεύστε εάν ο τρέχων χρήστης που έχει πρόσβαση στο σύστημα είναι ο χρήστης του συστήματος και επιβεβαιώστε ποιος χρήστης είναι.
  • Εξουσιοδότηση: Μετά τον έλεγχο ταυτότητας, καθορίστε εάν ο τρέχων χρήστης έχει άδεια να εκτελέσει μια λειτουργία

Ο έλεγχος ταυτότητας και η εξουσιοδότηση είναι οι βασικές λειτουργίες του Spring Security ως πλαίσιο ασφαλείας!

1. Γρήγορη εκκίνηση

Ας δημιουργήσουμε πρώτα απλά ένα έργο SpringBoot.

Αυτή τη στιγμή, έχουμε πρόσβαση σε μια απλή διεπαφή hello που γράψαμε για να επαληθεύσουμε εάν η κατασκευή είναι επιτυχής.

Στη συνέχεια, εισάγετε το SpringSecurity.

Αυτή τη στιγμή, ας ρίξουμε μια ματιά στο αποτέλεσμα της διεπαφής πρόσβασης.

Μετά την εισαγωγή του SpringSecurity, η διεπαφή πρόσβασης θα μεταβεί αυτόματα σε μια σελίδα σύνδεσης Το προεπιλεγμένο όνομα χρήστη είναι χρήστης και ο κωδικός πρόσβασης θα βγει στην κονσόλα.

2. Πιστοποίηση

2.1 Διαδικασία επαλήθευσης σύνδεσης

Πρώτα απ 'όλα, πρέπει να κατανοήσουμε τη διαδικασία επαλήθευσης σύνδεσης Πρώτα, η διεπαφή φέρει το όνομα χρήστη και τον κωδικό πρόσβασης για να αποκτήσει πρόσβαση στη διεπαφή σύνδεσης, αφού ο διακομιστής λάβει το όνομα χρήστη και τον κωδικό πρόσβασης. Εάν το όνομα χρήστη/το αναγνωριστικό χρήστη χρησιμοποιείται σωστά, δημιουργείται ένα jwt και, στη συνέχεια, το Respond jwt στη διεπαφή και, στη συνέχεια, η πρόσβαση σε άλλα αιτήματα μετά τη σύνδεση θα φέρει το διακριτικό στην κεφαλίδα αιτήματος Κάθε φορά που ο διακομιστής λαμβάνει το διακριτικό η κεφαλίδα αιτήματος για ανάλυση, αποκτά το UserID και λαμβάνει πληροφορίες σχετικά με το χρήστη και δικαιώματα προβολής με βάση το αναγνωριστικό ονόματος χρήστη, εάν έχετε άδεια, απαντήστε στη διεπαφή.

2.2 Προκαταρκτική διερεύνηση της αρχής

Η αρχή του SpringSecurity είναι στην πραγματικότητα μια αλυσίδα φίλτρων, η οποία παρέχει φίλτρα με διάφορες λειτουργίες Εδώ πρώτα εξετάζουμε τα φίλτρα που εμπλέκονται στη γρήγορη εκκίνηση.

  • Το UsernamePasswordAuthenticationFilter είναι υπεύθυνο για την επεξεργασία των αιτημάτων σύνδεσης μετά τη συμπλήρωση του ονόματος χρήστη και του κωδικού πρόσβασης στη σελίδα σύνδεσης.
  • Το ExceptionTranslationFilter χειρίζεται τυχόν AccessDeniedException και AuthenticationException που έχουν τοποθετηθεί στην αλυσίδα φίλτρων
  • Το FilterSecurityInterceptor είναι ένα φίλτρο υπεύθυνο για την επαλήθευση αδειών

Μπορούμε επίσης να χρησιμοποιήσουμε το Debug για να δούμε ποια φίλτρα βρίσκονται στην αλυσίδα φίλτρων SpringSecurity στο τρέχον σύστημα και τη σειρά τους.

Στη συνέχεια, ας ρίξουμε μια ματιά στην ανάλυση του διαγράμματος ροής ελέγχου ταυτότητας.

Εδώ χρειάζεται μόνο να είμαστε σε θέση να κατανοήσουμε τη διαδικασία για να το θέσω απλά:

Ο χρήστης υπέβαλε το όνομα χρήστη και τον κωδικό πρόσβασης, το UsernamePasswordAuthenticationFilter το ενσωματώνει ως αντικείμενο ελέγχου ταυτότητας και καλεί τη μέθοδο ελέγχου ταυτότητας για έλεγχο ταυτότητας είναι να κάνετε αναζήτηση στη μνήμη και, στη συνέχεια, να ενσωματώσετε τις αντίστοιχες πληροφορίες χρήστη σε ένα αντικείμενο UserDetails, χρησιμοποιήστε το PasswordEncoder για να συγκρίνετε τον κωδικό πρόσβασης στο UserDetails και τον κωδικό πρόσβασης ελέγχου ταυτότητας για να δείτε εάν είναι σωστός, ορίστε τις πληροφορίες άδειας στο UserDetails στο αντικείμενο Authentication , στη συνέχεια επιστρέψτε το αντικείμενο ελέγχου ταυτότητας και, τέλος, χρησιμοποιήστε το SecurityContextHolder.getContext( ).setAuthentication Μέθοδος Authentication αποθηκεύει αυτό το αντικείμενο και άλλα φίλτρα θα λάβουν τις τρέχουσες πληροφορίες χρήστη μέσω του SecurityContextHoder. (Δεν χρειάζεται να απομνημονεύσετε αυτήν την παράγραφο για να την κατανοήσετε)

Στη συνέχεια, γνωρίζουμε τη διαδικασία για να μπορέσουμε να την τροποποιήσουμε Πρώτα απ 'όλα, κατά την αναζήτηση από τη μνήμη, πρέπει να κάνουμε αναζήτηση από τη βάση δεδομένων (εδώ πρέπει να προσαρμόσουμε μια κλάση υλοποίησης UserDetailsService) και δεν θα χρησιμοποιήσουμε το προεπιλεγμένο όνομα χρήστη και κωδικό πρόσβασης. , και η διεπαφή σύνδεσης πρέπει να έχει γραφτεί από εσάς και δεν χρειάζεται να χρησιμοποιήσετε την προεπιλεγμένη σελίδα σύνδεσης που παρέχεται από αυτόν.

Με βάση την κατάσταση που αναλύσαμε, μπορούμε να έχουμε μια τέτοια εικόνα.

Αυτή τη στιγμή, ένα jwt επιστρέφεται στο μπροστινό μέρος και άλλα αιτήματα που γίνονται από το μπροστινό μέρος θα φέρουν το διακριτικό. Επομένως, το πρώτο μας βήμα είναι να ελέγξουμε εάν το διακριτικό μεταφέρεται, να αναλύσουμε το διακριτικό, να αποκτήσουμε το αντίστοιχο userid και να ενσωματώσουμε το διακριτικό. Το αντικείμενο Anthentication είναι αποθηκευμένο στο SecurityContextHolder (ώστε να μπορούν να το λάβουν άλλα φίλτρα).

Υπάρχει λοιπόν μια άλλη ερώτηση εδώ Πώς να αποκτήσετε πλήρεις πληροφορίες χρήστη μετά τη λήψη του userid από το φίλτρο ελέγχου ταυτότητας jwt;

Εδώ χρησιμοποιούμε redis Όταν ο διακομιστής πραγματοποιεί έλεγχο ταυτότητας χρησιμοποιώντας το αναγνωριστικό χρήστη για τη δημιουργία jwt στο μπροστινό μέρος, το αναγνωριστικό χρήστη χρησιμοποιείται ως κλειδί και οι πληροφορίες του χρήστη αποθηκεύονται σε redis ως τιμή από το redis μέσω του userid.

2.3 Λύστε το πρόβλημα

2.3.1 Ανάλυση ιδεών

Από την προκαταρκτική διερεύνηση των παραπάνω αρχών, έχουμε επίσης αναλύσει χονδρικά τι πρέπει να κάνουμε εάν εφαρμόσουμε μόνοι μας τη διαδικασία ελέγχου ταυτότητας διαχωρισμού front-end και back-end.

Σύνδεση:

α. Προσαρμοσμένη διεπαφή σύνδεσης

Καλέστε τη μέθοδο ProviderManager για έλεγχο ταυτότητας Εάν ο έλεγχος ταυτότητας περάσει, δημιουργείται ένα jwt.

Αποθηκεύστε τις πληροφορίες χρήστη σε redis

β. Προσαρμογή UserDetailsService

Υποβάλετε ερώτημα στη βάση δεδομένων σε αυτήν την κλάση υλοποίησης

έλεγχος:

α. Προσαρμόστε το φίλτρο ελέγχου ταυτότητας jwt

Λάβετε διακριτικό

Αναλύστε το διακριτικό για να αποκτήσετε το userid του

Λάβετε πλήρεις πληροφορίες χρήστη από το redis

Αποθηκεύστε στο SecurityContextHolder

2.3.2 Προετοιμασία

Πρώτα πρέπει να προσθέσετε τις αντίστοιχες εξαρτήσεις

  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.

  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 RedisCache, η οποία μας διευκολύνει να καλέσουμε το reistemplate

  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. }

Μπορεί επίσης να γράψουμε δεδομένα στην απόκριση, επομένως χρειαζόμαστε επίσης μια κλάση εργαλείων 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. }

Σύμφωνα με την παραπάνω ανάλυσή μας, πρέπει να προσαρμόσουμε ένα UserDetailsService για να επιτρέψουμε στο SpringSecuriry να χρησιμοποιήσει το UserDetailsService μας. Το δικό μας UserDetailsService μπορεί να ζητήσει το όνομα χρήστη και τον κωδικό πρόσβασης από τη βάση δεδομένων.

Πρώτα δημιουργούμε έναν πίνακα βάσης δεδομένων sys_user.

  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>

Στη συνέχεια διαμορφώστε τις σχετικές πληροφορίες της βάσης δεδομένων.

Στη συνέχεια, ορίστε τη διεπαφή χαρτογράφησης 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 μπορεί να χρησιμοποιηθεί κανονικά.

Εισάγετε το junit

Με αυτόν τον τρόπο μπορεί να χρησιμοποιηθεί κανονικά.

2.3.3 Εφαρμογή

2.3.3.1 Χρήστης επαλήθευσης βάσης δεδομένων

Στη συνέχεια πρέπει να εφαρμόσουμε τον βασικό κώδικα.

Αρχικά, ας προσαρμόσουμε το 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. }

Εδώ ο χρήστης ενθυλακώνεται ως UserDetails και επιστρέφεται.

  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} πριν από τον κωδικό πρόσβασης.

Εδώ μπορείτε να εισάγετε το όνομα χρήστη και τον κωδικό πρόσβασης στη βάση δεδομένων για να συνδεθείτε.

2.3.3.2 Αποθήκευση κρυπτογραφημένη με κωδικό πρόσβασης

Ας μιλήσουμε για το γιατί το {noop} προστίθεται μπροστά από τον κωδικό πρόσβασης, επειδή το προεπιλεγμένο PasswordEncoder απαιτεί τη μορφή κωδικού πρόσβασης στη βάση δεδομένων να είναι {id}password. Θα καθορίσει τη μέθοδο κρυπτογράφησης του κωδικού πρόσβασης με βάση το αναγνωριστικό Μην υιοθετήσετε αυτήν τη μέθοδο, επομένως το PasswordEncoder πρέπει να αντικατασταθεί.

Στη συνέχεια θα το δοκιμάσουμε και θα δούμε.

Μπορείτε να δείτε ότι οι δύο αρχικοί κωδικοί πρόσβασης που δώσαμε εδώ είναι οι ίδιοι, αλλά πήραμε διαφορετικά αποτελέσματα. Αυτό στην πραγματικότητα σχετίζεται με τον αλγόριθμο αλατισμού.

Αφού λάβετε τον κρυπτογραφημένο κωδικό πρόσβασης, μπορείτε να αποθηκεύσετε τον κρυπτογραφημένο κωδικό πρόσβασης στη βάση δεδομένων. Στη συνέχεια, μπορείτε να συνδεθείτε επαληθεύοντας τον κωδικό πρόσβασης απλού κειμένου που έχει περάσει από το μπροστινό μέρος με τον κρυπτογραφημένο κωδικό πρόσβασης στη βάση δεδομένων.

Αυτή τη στιγμή, ξεκινήσαμε το έργο για να συνδεθείτε και διαπιστώσαμε ότι δεν μπορούσαμε πλέον να συνδεθούμε με τον προηγούμενο κωδικό πρόσβασης, επειδή η βάση δεδομένων θα πρέπει να αποθηκεύει τον κρυπτογραφημένο κωδικό πρόσβασης που είναι αποθηκευμένος στη βάση δεδομένων κατά τη φάση εγγραφής, όχι τον αρχικό κωδικό πρόσβασης (γιατί εγώ δεν εγγράφηκα, θα κρυπτογραφήσω τον κωδικό πρόσβασης) Ο κωδικός πρόσβασης γράφεται στη βάση δεδομένων από μόνος του).

2.3.3.3 Διεπαφή σύνδεσης

Πρέπει να εφαρμόσουμε μια διεπαφή σύνδεσης και στη συνέχεια να αφήσουμε το SpringSecuruty να το επιτρέψει, εάν δεν επιτρέπεται, θα είναι αντιφατικό στη διεπαφή, ο έλεγχος ταυτότητας χρήστη πραγματοποιείται μέσω της μεθόδου ελέγχου ταυτότητας του AuthenticationManager, επομένως πρέπει να ρυθμίσουμε τις παραμέτρους του AuthenticationManager. στο κοντέινερ στο SecurityConfig.

Εάν ο έλεγχος ταυτότητας είναι επιτυχής, πρέπει να δημιουργηθεί ένα jwt και να τοποθετηθεί στην απόκριση Προκειμένου ο χρήστης να αναγνωρίσει τον συγκεκριμένο χρήστη μέσω του jwt κατά την υποβολή του επόμενου αιτήματος, οι πληροφορίες χρήστη πρέπει να αποθηκευτούν σε redis και ο χρήστης. Το ID μπορεί να χρησιμοποιηθεί ως κλειδί.

Γράψτε πρώτα το LoginController

Στη συνέχεια γράψτε την αντίστοιχη Υπηρεσία.

Εισαγάγετε το AuthenticationManager στο SecurityConfig και απελευθερώστε τη διεπαφή σύνδεσης.

Στην επιχειρηματική λογική της υπηρεσίας, εάν ο έλεγχος ταυτότητας αποτύχει, θα επιστραφεί μια προσαρμοσμένη εξαίρεση, αλλά εάν ο έλεγχος ταυτότητας είναι επιτυχής, πώς λαμβάνουμε τις αντίστοιχες πληροφορίες.

Εδώ μπορούμε να κάνουμε εντοπισμό σφαλμάτων και να δούμε τα ληφθέντα αντικείμενα.

Εδώ διαπιστώνεται ότι οι αντίστοιχες απαιτούμενες πληροφορίες μπορούν να ληφθούν στον Κύριο.

Στη συνέχεια συμπληρώστε τον κωδικό.

Τέλος, δοκιμάστε το.

2.3.3.4 Φίλτρο ελέγχου ταυτότητας

Θα επικολλήσω πρώτα τον κωδικό.

  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 μέσα και στη συνέχεια θα χρησιμοποιήσει το userid με βάση το Αποκτήστε τις αντίστοιχες πληροφορίες χρήστη από το redis και τελικά θα το αποθηκεύσει στο SecurityContextHolder, επειδή τα επόμενα φίλτρα πρέπει να λάβουν τις καθημερινές πληροφορίες ελέγχου ταυτότητας από αυτό και τελικά να εκτελέσουν εργασίες ανάλυσης.

Ένα άλλο σημείο που χρειάζεται προσοχή είναι ότι το SecurityContextHolder.getContext().setAuthentication() πρέπει να περάσει στο αντικείμενο ελέγχου ταυτότητας Όταν χτίζουμε το αντικείμενο, χρησιμοποιούμε τρεις παραμέτρους, επειδή η τρίτη παράμετρος είναι το κλειδί για να καθορίσουμε αν θα γίνει έλεγχος ταυτότητας ή όχι.

Στη συνέχεια πρέπει να διαμορφώσουμε αυτό το φίλτρο.

Στη συνέχεια, όταν αποκτήσουμε πρόσβαση στη διεπαφή χρήστη/σύνδεσης, ένα σώμα απόκρισης με διακριτικό θα επιστραφεί σε εμάς Όταν αποκτήσουμε ξανά πρόσβαση στη διεπαφή hello, θα είναι 403. Επειδή δεν φέρει διακριτικό, αντιστοιχεί στον παραπάνω κωδικό. Χωρίς διακριτικό, το σώμα απόκρισης θα απελευθερωθεί και η επιστροφή δεν θα εκτελέσει την επακόλουθη διαδικασία (Η έκδοση εδώ οφείλεται στο ότι υπάρχουν άλλα φίλτρα που δημιουργούν εξαιρέσεις για επεξεργασία αργότερα και η επιστροφή είναι για να μην περάσει από την απόκριση. επεξεργάζομαι, διαδικασία)

Αυτή τη στιγμή, εάν βάλουμε το διακριτικό που δημιουργείται από χρήστη/σύνδεση στην κεφαλίδα αιτήματος της διεπαφής hello, μπορούμε να έχουμε πρόσβαση κανονικά.

Στη συνέχεια, ο σκοπός του συνόλου των φίλτρων μας έχει επιτευχθεί (απόκτηση διακριτικών, ανάλυση διακριτικών και αποθήκευση στο SecurityContextHolder)

2.3.3.5 Αποσύνδεση

Σε αυτό το σημείο, είναι πιο εύκολο για εμάς να αποσυνδεθούμε. Πρέπει να διαγράψουμε μόνο τα αντίστοιχα δεδομένα στο redis δεν μπορείτε να το λάβετε, σημαίνει ότι δεν είστε συνδεδεμένοι.

Φέρνουμε αυτό το διακριτικό για πρόσβαση στη διεπαφή /user/logout.

Στη συνέχεια υλοποιείται η συνάρτηση αποσύνδεσης.

Αυτό το άρθρο μαθεύτηκε από την τρίτη ενημέρωση του σταθμού β! ! !