τα στοιχεία επικοινωνίας μου
Ταχυδρομείο[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Πίνακας περιεχομένων
2.1 Διαδικασία επαλήθευσης σύνδεσης
2.2 Προκαταρκτική διερεύνηση της αρχής
2.3.3.1 Χρήστης επαλήθευσης βάσης δεδομένων
2.3.3.2 Αποθήκευση κρυπτογραφημένη με κωδικό πρόσβασης
2.3.3.4 Φίλτρο ελέγχου ταυτότητας
Το Spring Security είναι ένα πλαίσιο διαχείρισης ασφάλειας στην οικογένεια Spring Σε σύγκριση με το Shiro, ένα άλλο πλαίσιο ασφαλείας, παρέχει πλουσιότερες λειτουργίες και πλουσιότερους πόρους κοινότητας από το Shiro.
Σε γενικές γραμμές, το Spring Security χρησιμοποιείται πιο συχνά σε έργα μεγάλης κλίμακας και το Shiro χρησιμοποιείται πιο συχνά σε μικρά έργα, επειδή σε σύγκριση με το Spring Security, το Shiro είναι πιο εύκολο να ξεκινήσετε.
Οι γενικές εφαρμογές web πρέπει ναΠιστοποίησηκαιΕξουσιοδοτώ。
Ο έλεγχος ταυτότητας και η εξουσιοδότηση είναι οι βασικές λειτουργίες του Spring Security ως πλαίσιο ασφαλείας!
Ας δημιουργήσουμε πρώτα απλά ένα έργο SpringBoot.
Αυτή τη στιγμή, έχουμε πρόσβαση σε μια απλή διεπαφή hello που γράψαμε για να επαληθεύσουμε εάν η κατασκευή είναι επιτυχής.
Στη συνέχεια, εισάγετε το SpringSecurity.
Αυτή τη στιγμή, ας ρίξουμε μια ματιά στο αποτέλεσμα της διεπαφής πρόσβασης.
Μετά την εισαγωγή του SpringSecurity, η διεπαφή πρόσβασης θα μεταβεί αυτόματα σε μια σελίδα σύνδεσης Το προεπιλεγμένο όνομα χρήστη είναι χρήστης και ο κωδικός πρόσβασης θα βγει στην κονσόλα.
Πρώτα απ 'όλα, πρέπει να κατανοήσουμε τη διαδικασία επαλήθευσης σύνδεσης Πρώτα, η διεπαφή φέρει το όνομα χρήστη και τον κωδικό πρόσβασης για να αποκτήσει πρόσβαση στη διεπαφή σύνδεσης, αφού ο διακομιστής λάβει το όνομα χρήστη και τον κωδικό πρόσβασης. Εάν το όνομα χρήστη/το αναγνωριστικό χρήστη χρησιμοποιείται σωστά, δημιουργείται ένα jwt και, στη συνέχεια, το Respond jwt στη διεπαφή και, στη συνέχεια, η πρόσβαση σε άλλα αιτήματα μετά τη σύνδεση θα φέρει το διακριτικό στην κεφαλίδα αιτήματος Κάθε φορά που ο διακομιστής λαμβάνει το διακριτικό η κεφαλίδα αιτήματος για ανάλυση, αποκτά το UserID και λαμβάνει πληροφορίες σχετικά με το χρήστη και δικαιώματα προβολής με βάση το αναγνωριστικό ονόματος χρήστη, εάν έχετε άδεια, απαντήστε στη διεπαφή.
Η αρχή του SpringSecurity είναι στην πραγματικότητα μια αλυσίδα φίλτρων, η οποία παρέχει φίλτρα με διάφορες λειτουργίες Εδώ πρώτα εξετάζουμε τα φίλτρα που εμπλέκονται στη γρήγορη εκκίνηση.
Μπορούμε επίσης να χρησιμοποιήσουμε το 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.
Από την προκαταρκτική διερεύνηση των παραπάνω αρχών, έχουμε επίσης αναλύσει χονδρικά τι πρέπει να κάνουμε εάν εφαρμόσουμε μόνοι μας τη διαδικασία ελέγχου ταυτότητας διαχωρισμού front-end και back-end.
Σύνδεση:
α. Προσαρμοσμένη διεπαφή σύνδεσης
Καλέστε τη μέθοδο ProviderManager για έλεγχο ταυτότητας Εάν ο έλεγχος ταυτότητας περάσει, δημιουργείται ένα jwt.
Αποθηκεύστε τις πληροφορίες χρήστη σε redis
β. Προσαρμογή UserDetailsService
Υποβάλετε ερώτημα στη βάση δεδομένων σε αυτήν την κλάση υλοποίησης
έλεγχος:
α. Προσαρμόστε το φίλτρο ελέγχου ταυτότητας jwt
Λάβετε διακριτικό
Αναλύστε το διακριτικό για να αποκτήσετε το userid του
Λάβετε πλήρεις πληροφορίες χρήστη από το redis
Αποθηκεύστε στο SecurityContextHolder
Πρώτα πρέπει να προσθέσετε τις αντίστοιχες εξαρτήσεις
- <!-- SpringSecurity启动器 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
-
- <!-- redis依赖 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- fastjson依赖 -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.33</version>
- </dependency>
- <!-- jwt依赖 -->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
Στη συνέχεια, πρέπει να χρησιμοποιήσουμε το Redis και να προσθέσουμε διαμορφώσεις σχετικές με το Redis.
Πρώτα είναι ο σειριακός του FastJson
- package org.example.utils;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.parser.ParserConfig;
- import com.alibaba.fastjson.serializer.SerializerFeature;
- import com.fasterxml.jackson.databind.JavaType;
- import com.fasterxml.jackson.databind.type.TypeFactory;
- import org.springframework.data.redis.serializer.RedisSerializer;
- import org.springframework.data.redis.serializer.SerializationException;
-
- import java.nio.charset.Charset;
-
- /**
- * Redis使用fastjson序列化
- * @param <T>
- */
- public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
-
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
- private Class<T> clazz;
-
- static {
- ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
- }
-
- public FastJsonRedisSerializer(Class<T> clazz){
- super();
- this.clazz=clazz;
- }
-
- @Override
- public byte[] serialize(T t) throws SerializationException {
- if (t == null){
- return new byte[0];
- }
- return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
- }
-
- @Override
- public T deserialize(byte[] bytes) throws SerializationException {
- if (bytes==null || bytes.length<=0){
- return null;
- }
- String str = new String(bytes,DEFAULT_CHARSET);
-
- return JSON.parseObject(str,clazz);
- }
-
- protected JavaType getJavaType(Class<?> clazz){
- return TypeFactory.defaultInstance().constructType(clazz);
- }
-
- }
Δημιουργήστε το RedisConfig και δημιουργήστε ένα σειριακό πρόγραμμα σε αυτό για να λύσετε προβλήματα όπως μπερδεμένους χαρακτήρες.
- package org.example.config;
- import org.example.utils.FastJsonRedisSerializer;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.redis.connection.RedisConnectionFactory;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.serializer.StringRedisSerializer;
-
- @Configuration
- public class RedisConfig {
- @Bean
- @SuppressWarnings(value = {"unchecked","rawtypes"})
- public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
- RedisTemplate<Object,Object> template = new RedisTemplate<>();
- template.setConnectionFactory(connectionFactory);
-
- FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
-
- //使用StringRedisSerializer来序列化和反序列化redus的key值
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(serializer);
-
- template.afterPropertiesSet();
- return template;
- }
- }
Είναι επίσης απαραίτητο να ενοποιηθεί η κλάση απόκρισης
- package org.example.domain;
- import com.fasterxml.jackson.annotation.JsonInclude;
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public class ResponseResult<T>{
- /**
- * 状态码
- */
- private Integer code;
- /**
- * 提示信息,如果有错误时,前端可以获取该字段进行提示
- */
- private String msg;
- /**
- * 查询到的结果数据
- */
- private T data;
-
- public ResponseResult(Integer code,String msg){
- this.code = code;
- this.msg = msg;
- }
-
- public ResponseResult(Integer code,T data){
- this.code = code;
- this.data = data;
- }
-
- public Integer getCode() {
- return code;
- }
-
- public void setCode(Integer code) {
- this.code = code;
- }
-
- public String getMsg() {
- return msg;
- }
-
- public void setMsg(String msg) {
- this.msg = msg;
- }
-
- public T getData() {
- return data;
- }
-
- public void setData(T data) {
- this.data = data;
- }
-
- public ResponseResult(Integer code,String msg,T data){
- this.code = code;
- this.msg = msg;
- this.data = data;
- }
- }
Χρειάζεστε κλάσεις εργαλείων jwt για να δημιουργήσετε jwt και να αναλύσετε το jwt.
- package org.example.utils;
-
- 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;
-
- public class JwtUtil {
-
- //有效期为
- public static final Long JWT_TTL = 60*60*1000L; //一个小时
- //设置密钥明文
- public static final String JWT_KEY = "hzj";
-
- public static String getUUID(){
- String token = UUID.randomUUID().toString().replaceAll("-","");
- return token;
- }
-
- /**
- * 生成jwt
- * @param subject token中要存放的数据(json格式)
- * @return
- */
- public static String createJWT(String subject){
- JwtBuilder builder = getJwtBuilder(subject,null,getUUID()); //设置过期时间
- return builder.compact();
- }
-
- /**
- * 生成jwt
- * @param subject token中要存放的数据(json格式)
- * @param ttlMillis token超时时间
- * @return
- */
- public static String createJWT(String subject,Long ttlMillis){
- JwtBuilder builder = getJwtBuilder(subject,ttlMillis,getUUID()); //设置过期时间
- 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数据
- .setIssuer("hzj") //签发者
- .setIssuedAt(now) //签发时间
- .signWith(signatureAlgorithm,secretKey) //使用HS256对称加密算法签名,第二个参数为密钥
- .setExpiration(expDate);
- }
-
- /**
- * 创建token
- * @param id
- * @param subject
- * @param ttlMillis
- * @return
- */
- public static String createJWT(String id,String subject,Long ttlMillis){
- JwtBuilder builder = getJwtBuilder(subject,ttlMillis,id);//设置过期时间
- return builder.compact();
- }
-
- public static void main(String[] args) throws Exception{
- String token =
- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTg0MjU5MzIsInVzZX" +
- "JJZCI6MTExLCJ1c2VybmFtZSI6Ik1hcmtaUVAifQ.PTlOdRG7ROVJqPrA0q2ac7rKFzNNFR3lTMyP_8fIw9Q";
- Claims claims = parseJWT(token);
- System.out.println(claims);
- }
-
- /**
- * 生成加密后的密钥secretkey
- * @return
- */
- public static SecretKey generalkey(){
- byte[] encodeedkey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
- SecretKey key = new SecretKeySpec(encodeedkey,0,encodeedkey.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();
- }
- }
Ορίστε μια άλλη κλάση εργαλείων Redis RedisCache, η οποία μας διευκολύνει να καλέσουμε το reistemplate
- package org.example.utils;
-
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.BoundSetOperations;
- import org.springframework.data.redis.core.HashOperations;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.ValueOperations;
- import org.springframework.stereotype.Component;
-
- import java.util.*;
- import java.util.concurrent.TimeUnit;
-
- @SuppressWarnings(value = { "unchecked", "rawtypes" })
- @Component
- public class RedisCache
- {
- @Autowired
- public RedisTemplate redisTemplate;
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- */
- public <T> void setCacheObject(final String key, final T value)
- {
- redisTemplate.opsForValue().set(key, value);
- }
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- * @param timeout 时间
- * @param timeUnit 时间颗粒度
- */
- public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
- {
- redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout)
- {
- return expire(key, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @param unit 时间单位
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout, final TimeUnit unit)
- {
- return redisTemplate.expire(key, timeout, unit);
- }
-
- /**
- * 获得缓存的基本对象。
- *
- * @param key 缓存键值
- * @return 缓存键值对应的数据
- */
- public <T> T getCacheObject(final String key)
- {
- ValueOperations<String, T> operation = redisTemplate.opsForValue();
- return operation.get(key);
- }
-
- /**
- * 删除单个对象
- *
- * @param key
- */
- public boolean deleteObject(final String key)
- {
- return redisTemplate.delete(key);
- }
-
- /**
- * 删除集合对象
- *
- * @param collection 多个对象
- * @return
- */
- public long deleteObject(final Collection collection)
- {
- return redisTemplate.delete(collection);
- }
-
- /**
- * 缓存List数据
- *
- * @param key 缓存的键值
- * @param dataList 待缓存的List数据
- * @return 缓存的对象
- */
- public <T> long setCacheList(final String key, final List<T> dataList)
- {
- Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
- return count == null ? 0 : count;
- }
-
- /**
- * 获得缓存的list对象
- *
- * @param key 缓存的键值
- * @return 缓存键值对应的数据
- */
- public <T> List<T> getCacheList(final String key)
- {
- return redisTemplate.opsForList().range(key, 0, -1);
- }
-
- /**
- * 缓存Set
- *
- * @param key 缓存键值
- * @param dataSet 缓存的数据
- * @return 缓存数据的对象
- */
- public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
- {
- BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
- Iterator<T> it = dataSet.iterator();
- while (it.hasNext())
- {
- setOperation.add(it.next());
- }
- return setOperation;
- }
-
- /**
- * 获得缓存的set
- *
- * @param key
- * @return
- */
- public <T> Set<T> getCacheSet(final String key)
- {
- return redisTemplate.opsForSet().members(key);
- }
-
- /**
- * 缓存Map
- *
- * @param key
- * @param dataMap
- */
- public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
- {
- if (dataMap != null) {
- redisTemplate.opsForHash().putAll(key, dataMap);
- }
- }
-
- /**
- * 获得缓存的Map
- *
- * @param key
- * @return
- */
- public <T> Map<String, T> getCacheMap(final String key)
- {
- return redisTemplate.opsForHash().entries(key);
- }
-
- /**
- * 往Hash中存入数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @param value 值
- */
- public <T> void setCacheMapValue(final String key, final String hKey, final T value)
- {
- redisTemplate.opsForHash().put(key, hKey, value);
- }
-
- /**
- * 获取Hash中的数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @return Hash中的对象
- */
- public <T> T getCacheMapValue(final String key, final String hKey)
- {
- HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
- return opsForHash.get(key, hKey);
- }
-
- /**
- * 删除Hash中的数据
- *
- * @param key
- * @param hkey
- */
- public void delCacheMapValue(final String key, final String hkey)
- {
- HashOperations hashOperations = redisTemplate.opsForHash();
- hashOperations.delete(key, hkey);
- }
-
- /**
- * 获取多个Hash中的数据
- *
- * @param key Redis键
- * @param hKeys Hash键集合
- * @return Hash对象集合
- */
- public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
- {
- return redisTemplate.opsForHash().multiGet(key, hKeys);
- }
-
- /**
- * 获得缓存的基本对象列表
- *
- * @param pattern 字符串前缀
- * @return 对象列表
- */
- public Collection<String> keys(final String pattern)
- {
- return redisTemplate.keys(pattern);
- }
- }
-
Μπορεί επίσης να γράψουμε δεδομένα στην απόκριση, επομένως χρειαζόμαστε επίσης μια κλάση εργαλείων WebUtils
- package org.example.utils;
-
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- public class WebUtils {
- /**
- * 将字符串渲染到客户端
- *
- * @param response 渲染对象
- * @param string 待渲染的字符串
- * @return null
- */
- public static String renderString(HttpServletResponse response, String string) {
- try
- {
- response.setStatus(200);
- response.setContentType("application/json");
- response.setCharacterEncoding("utf-8");
- response.getWriter().print(string);
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- return null;
- }
- }
Τέλος γράψτε την αντίστοιχη κλάση οντοτήτων χρήστη
- package org.example.domain;
-
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import java.io.Serializable;
- import java.util.Date;
- /**
- * 用户表(User)实体类
- */
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class User implements Serializable {
- private static final long serialVersionUID = -40356785423868312L;
- /**
- * 主键
- */
- private Long id;
- /**
- * 用户名
- */
- private String userName;
- /**
- * 昵称
- */
- private String nickName;
- /**
- * 密码
- */
- private String password;
- /**
- * 账号状态(0正常 1停用)
- */
- private String status;
- /**
- * 邮箱
- */
- private String email;
- /**
- * 手机号
- */
- private String phonenumber;
- /**
- * 用户性别(0男,1女,2未知)
- */
- private String sex;
- /**
- * 头像
- */
- private String avatar;
- /**
- * 用户类型(0管理员,1普通用户)
- */
- private String userType;
- /**
- * 创建人的用户id
- */
- private Long createBy;
- /**
- * 创建时间
- */
- private Date createTime;
- /**
- * 更新人
- */
- private Long updateBy;
- /**
- * 更新时间
- */
- private Date updateTime;
- /**
- * 删除标志(0代表未删除,1代表已删除)
- */
- private Integer delFlag;
- }
-
Σύμφωνα με την παραπάνω ανάλυσή μας, πρέπει να προσαρμόσουμε ένα UserDetailsService για να επιτρέψουμε στο SpringSecuriry να χρησιμοποιήσει το UserDetailsService μας. Το δικό μας UserDetailsService μπορεί να ζητήσει το όνομα χρήστη και τον κωδικό πρόσβασης από τη βάση δεδομένων.
Πρώτα δημιουργούμε έναν πίνακα βάσης δεδομένων sys_user.
- CREATE TABLE `sys_user` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
- `user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
- `nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '呢称',
- `password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
- `status` char(1) DEFAULT '0' COMMENT '账号状态(0正常1停用)',
- `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
- `phonenumber` varchar(32) DEFAULT NULL COMMENT '手机号',
- `sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
- `avatar` varchar(128) DEFAULT NULL COMMENT '头像',
- `user_type` char(1) NOT NULL DEFAULT '1' COMMENT '用户类型(O管理员,1普通用户)',
- `create_by` bigint DEFAULT NULL COMMENT '创建人的用户id',
- `create_time` datetime DEFAULT NULL COMMENT '创建时间',
- `update_by` bigint DEFAULT NULL COMMENT '更新人',
- `update_time` datetime DEFAULT NULL COMMENT '更新时间',
- `del_flag` int DEFAULT '0' COMMENT '删除标志(O代表未删除,1代表已删除)',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
Στη συνέχεια, εισάγετε τα προγράμματα οδήγησης myBatisPlus και mysql.
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.3</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
Στη συνέχεια διαμορφώστε τις σχετικές πληροφορίες της βάσης δεδομένων.
Στη συνέχεια, ορίστε τη διεπαφή χαρτογράφησης UserMapper και χρησιμοποιήστε το mybatisplus για να προσθέσετε αντίστοιχους σχολιασμούς.
- package org.example.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import org.example.domain.User;
-
- public interface UserMapper extends BaseMapper<User> {
- }
Στη συνέχεια, διαμορφώστε τη σάρωση στοιχείων
Τέλος, ελέγξτε αν το mp μπορεί να χρησιμοποιηθεί κανονικά.
Εισάγετε το junit
Με αυτόν τον τρόπο μπορεί να χρησιμοποιηθεί κανονικά.
Στη συνέχεια πρέπει να εφαρμόσουμε τον βασικό κώδικα.
Αρχικά, ας προσαρμόσουμε το UserDetailsService.
- package org.example.service.impl;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import org.example.domain.LoginUser;
- import org.example.domain.User;
- import org.example.mapper.UserMapper;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
- import java.util.Objects;
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
- @Autowired
- private UserMapper userMapper;
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- //查询用户信息 [InMemoryUserDetailsManager是在内存中查找]
- LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
- wrapper.eq(User::getUserName,username);
- User user = userMapper.selectOne(wrapper);
- //如果查询不到数据就抛出异常,给出提示
- if(Objects.isNull(user)){
- throw new RuntimeException("用户名或密码错误!");
- }
-
- //TODO 查询权限信息
-
- //封装为UserDetails对象返回
- return new LoginUser(user);
- }
- }
Εδώ ο χρήστης ενθυλακώνεται ως UserDetails και επιστρέφεται.
- package org.example.domain;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import java.util.Collection;
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class LoginUser implements UserDetails {
- private User user;
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return null;
- }
-
- @Override
- public String getPassword() {
- return user.getPassword();
- }
-
- @Override
- public String getUsername() {
- return user.getUserName();
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
Τέλος, υπάρχει ένα σημείο εδώ, δηλαδή, πρέπει να κάνουμε μια δοκιμή σύνδεσης για να λάβουμε δεδομένα από τη βάση δεδομένων Πρέπει να γράψουμε δεδομένα χρήστη στον πίνακα και εάν θέλετε ο κωδικός πρόσβασης του χρήστη να μεταδοθεί σε καθαρό κείμενο , πρέπει να προσθέσετε το {noop} πριν από τον κωδικό πρόσβασης.
Εδώ μπορείτε να εισάγετε το όνομα χρήστη και τον κωδικό πρόσβασης στη βάση δεδομένων για να συνδεθείτε.
Ας μιλήσουμε για το γιατί το {noop} προστίθεται μπροστά από τον κωδικό πρόσβασης, επειδή το προεπιλεγμένο PasswordEncoder απαιτεί τη μορφή κωδικού πρόσβασης στη βάση δεδομένων να είναι {id}password. Θα καθορίσει τη μέθοδο κρυπτογράφησης του κωδικού πρόσβασης με βάση το αναγνωριστικό Μην υιοθετήσετε αυτήν τη μέθοδο, επομένως το PasswordEncoder πρέπει να αντικατασταθεί.
Στη συνέχεια θα το δοκιμάσουμε και θα δούμε.
Μπορείτε να δείτε ότι οι δύο αρχικοί κωδικοί πρόσβασης που δώσαμε εδώ είναι οι ίδιοι, αλλά πήραμε διαφορετικά αποτελέσματα. Αυτό στην πραγματικότητα σχετίζεται με τον αλγόριθμο αλατισμού.
Αφού λάβετε τον κρυπτογραφημένο κωδικό πρόσβασης, μπορείτε να αποθηκεύσετε τον κρυπτογραφημένο κωδικό πρόσβασης στη βάση δεδομένων. Στη συνέχεια, μπορείτε να συνδεθείτε επαληθεύοντας τον κωδικό πρόσβασης απλού κειμένου που έχει περάσει από το μπροστινό μέρος με τον κρυπτογραφημένο κωδικό πρόσβασης στη βάση δεδομένων.
Αυτή τη στιγμή, ξεκινήσαμε το έργο για να συνδεθείτε και διαπιστώσαμε ότι δεν μπορούσαμε πλέον να συνδεθούμε με τον προηγούμενο κωδικό πρόσβασης, επειδή η βάση δεδομένων θα πρέπει να αποθηκεύει τον κρυπτογραφημένο κωδικό πρόσβασης που είναι αποθηκευμένος στη βάση δεδομένων κατά τη φάση εγγραφής, όχι τον αρχικό κωδικό πρόσβασης (γιατί εγώ δεν εγγράφηκα, θα κρυπτογραφήσω τον κωδικό πρόσβασης) Ο κωδικός πρόσβασης γράφεται στη βάση δεδομένων από μόνος του).
Πρέπει να εφαρμόσουμε μια διεπαφή σύνδεσης και στη συνέχεια να αφήσουμε το SpringSecuruty να το επιτρέψει, εάν δεν επιτρέπεται, θα είναι αντιφατικό στη διεπαφή, ο έλεγχος ταυτότητας χρήστη πραγματοποιείται μέσω της μεθόδου ελέγχου ταυτότητας του AuthenticationManager, επομένως πρέπει να ρυθμίσουμε τις παραμέτρους του AuthenticationManager. στο κοντέινερ στο SecurityConfig.
Εάν ο έλεγχος ταυτότητας είναι επιτυχής, πρέπει να δημιουργηθεί ένα jwt και να τοποθετηθεί στην απόκριση Προκειμένου ο χρήστης να αναγνωρίσει τον συγκεκριμένο χρήστη μέσω του jwt κατά την υποβολή του επόμενου αιτήματος, οι πληροφορίες χρήστη πρέπει να αποθηκευτούν σε redis και ο χρήστης. Το ID μπορεί να χρησιμοποιηθεί ως κλειδί.
Γράψτε πρώτα το LoginController
Στη συνέχεια γράψτε την αντίστοιχη Υπηρεσία.
Εισαγάγετε το AuthenticationManager στο SecurityConfig και απελευθερώστε τη διεπαφή σύνδεσης.
Στην επιχειρηματική λογική της υπηρεσίας, εάν ο έλεγχος ταυτότητας αποτύχει, θα επιστραφεί μια προσαρμοσμένη εξαίρεση, αλλά εάν ο έλεγχος ταυτότητας είναι επιτυχής, πώς λαμβάνουμε τις αντίστοιχες πληροφορίες.
Εδώ μπορούμε να κάνουμε εντοπισμό σφαλμάτων και να δούμε τα ληφθέντα αντικείμενα.
Εδώ διαπιστώνεται ότι οι αντίστοιχες απαιτούμενες πληροφορίες μπορούν να ληφθούν στον Κύριο.
Στη συνέχεια συμπληρώστε τον κωδικό.
Τέλος, δοκιμάστε το.
Θα επικολλήσω πρώτα τον κωδικό.
- @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为空 不执行后续流程
- }
- //解析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(因为过滤器链后面的filter都是从中获取认证信息进行对应放行)
- //TODO 获取权限信息封装到Authentication中
- UsernamePasswordAuthenticationToken authenticationToken =
- new UsernamePasswordAuthenticationToken(loginUser,null,null);
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
-
- //放行
- filterChain.doFilter(request,response); //此时的放行是携带认证的,不同于上方token为空的放行
- }
- }
Πρώτα απ 'όλα, για να λάβουμε το διακριτικό εδώ, παίρνουμε το αντίστοιχο διακριτικό από την κεφαλίδα του αιτήματος και, στη συνέχεια, ελέγχουμε ότι είναι κενό, θα το απελευθερώσουμε απευθείας χωρίς να περάσουμε στη διαδικασία παρακολούθησης θα αναλύσει το διακριτικό, θα πάρει το userid μέσα και στη συνέχεια θα χρησιμοποιήσει το userid με βάση το Αποκτήστε τις αντίστοιχες πληροφορίες χρήστη από το redis και τελικά θα το αποθηκεύσει στο SecurityContextHolder, επειδή τα επόμενα φίλτρα πρέπει να λάβουν τις καθημερινές πληροφορίες ελέγχου ταυτότητας από αυτό και τελικά να εκτελέσουν εργασίες ανάλυσης.
Ένα άλλο σημείο που χρειάζεται προσοχή είναι ότι το SecurityContextHolder.getContext().setAuthentication() πρέπει να περάσει στο αντικείμενο ελέγχου ταυτότητας Όταν χτίζουμε το αντικείμενο, χρησιμοποιούμε τρεις παραμέτρους, επειδή η τρίτη παράμετρος είναι το κλειδί για να καθορίσουμε αν θα γίνει έλεγχος ταυτότητας ή όχι.
Στη συνέχεια πρέπει να διαμορφώσουμε αυτό το φίλτρο.
Στη συνέχεια, όταν αποκτήσουμε πρόσβαση στη διεπαφή χρήστη/σύνδεσης, ένα σώμα απόκρισης με διακριτικό θα επιστραφεί σε εμάς Όταν αποκτήσουμε ξανά πρόσβαση στη διεπαφή hello, θα είναι 403. Επειδή δεν φέρει διακριτικό, αντιστοιχεί στον παραπάνω κωδικό. Χωρίς διακριτικό, το σώμα απόκρισης θα απελευθερωθεί και η επιστροφή δεν θα εκτελέσει την επακόλουθη διαδικασία (Η έκδοση εδώ οφείλεται στο ότι υπάρχουν άλλα φίλτρα που δημιουργούν εξαιρέσεις για επεξεργασία αργότερα και η επιστροφή είναι για να μην περάσει από την απόκριση. επεξεργάζομαι, διαδικασία)
Αυτή τη στιγμή, εάν βάλουμε το διακριτικό που δημιουργείται από χρήστη/σύνδεση στην κεφαλίδα αιτήματος της διεπαφής hello, μπορούμε να έχουμε πρόσβαση κανονικά.
Στη συνέχεια, ο σκοπός του συνόλου των φίλτρων μας έχει επιτευχθεί (απόκτηση διακριτικών, ανάλυση διακριτικών και αποθήκευση στο SecurityContextHolder)
Σε αυτό το σημείο, είναι πιο εύκολο για εμάς να αποσυνδεθούμε. Πρέπει να διαγράψουμε μόνο τα αντίστοιχα δεδομένα στο redis δεν μπορείτε να το λάβετε, σημαίνει ότι δεν είστε συνδεδεμένοι.
Φέρνουμε αυτό το διακριτικό για πρόσβαση στη διεπαφή /user/logout.
Στη συνέχεια υλοποιείται η συνάρτηση αποσύνδεσης.
Αυτό το άρθρο μαθεύτηκε από την τρίτη ενημέρωση του σταθμού β! ! !