informasi kontak saya
Surat[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Daftar isi
2.2 Eksplorasi awal prinsip tersebut
2.3.3.1 Pengguna verifikasi basis data
2.3.3.2 Penyimpanan terenkripsi kata sandi
Spring Security adalah kerangka manajemen keamanan dalam keluarga Spring. Dibandingkan dengan Shiro, kerangka keamanan lainnya, Spring Security menyediakan fungsi yang lebih kaya dan sumber daya komunitas yang lebih kaya daripada Shiro.
Secara umum, Spring Security lebih umum digunakan pada proyek skala besar, dan Shiro lebih umum digunakan pada proyek kecil, karena dibandingkan dengan Spring Security, Shiro lebih mudah untuk memulai.
Aplikasi web umum memerlukannyaSertifikasiDanMengizinkan。
Otentikasi dan otorisasi adalah fungsi inti Spring Security sebagai kerangka keamanan!
Pertama-tama mari kita buat proyek SpringBoot.
Saat ini, kami mengakses antarmuka hello sederhana yang kami tulis untuk memverifikasi apakah konstruksi berhasil.
Kemudian perkenalkan SpringSecurity.
Saat ini, mari kita lihat efek antarmuka akses.
Setelah SpringSecurity diperkenalkan, antarmuka akses akan secara otomatis melompat ke halaman login. Nama pengguna default adalah pengguna, dan kata sandi akan dikeluarkan ke konsol.
Pertama-tama, kita perlu memahami proses verifikasi login. Pertama, front end membawa nama pengguna dan kata sandi untuk mengakses antarmuka login. Setelah server mendapatkan nama pengguna dan kata sandi, server membandingkannya dengan yang ada di database. Jika nama pengguna/ID pengguna digunakan dengan benar, jwt dibuat, lalu Tanggapi jwt ke ujung depan, dan kemudian akses permintaan lain setelah masuk akan membawa token di header permintaan header permintaan untuk parsing, memperoleh UserID, dan memperoleh informasi terkait pengguna dan izin pemirsa berdasarkan id nama pengguna, jika Jika Anda memiliki izin, tanggapi ke ujung depan.
Prinsip SpringSecurity sebenarnya adalah rantai filter, yang menyediakan filter dengan berbagai fungsi. Di sini pertama-tama kita melihat filter yang terlibat dalam mulai cepat di atas.
Kita juga dapat menggunakan Debug untuk melihat filter mana yang ada dalam rantai filter SpringSecurity di sistem saat ini dan urutannya.
Selanjutnya, mari kita lihat analisis diagram alur otentikasi.
Di sini kita hanya perlu bisa memahami prosesnya.
Pengguna mengirimkan nama pengguna dan kata sandi, UsernamePasswordAuthenticationFilter merangkumnya sebagai objek Otentikasi, dan memanggil metode autentikasi untuk autentikasi. Kemudian memanggil metode autentikasi DaoAuthenticationProvider untuk autentikasi, dan kemudian memanggil metode loadUserByUserName untuk menanyakan kueri pengguna di sini adalah mencari di memori, lalu Enkapsulasi informasi pengguna yang sesuai ke dalam objek UserDetails, gunakan PasswordEncoder untuk membandingkan kata sandi di UserDetails dan kata sandi Otentikasi untuk melihat apakah sudah benar. Jika benar, atur informasi izin di UserDetails ke objek Otentikasi , lalu kembalikan objek Otentikasi, dan terakhir gunakan metode SecurityContextHolder.getContext().setAuthentication menyimpan objek ini, dan filter lain akan memperoleh informasi pengguna saat ini melalui SecurityContextHoder. (Anda tidak perlu menghafal paragraf ini untuk memahaminya)
Kemudian kita mengetahui prosesnya sebelum kita dapat memodifikasinya. Pertama-tama, ketika mencari dari memori, kita harus mencari dari database (di sini kita perlu menyesuaikan kelas implementasi UserDetailsService), dan kita tidak akan menggunakan nama pengguna dan kata sandi default. , dan antarmuka login harus ditulis sendiri, dan tidak perlu menggunakan halaman login default yang disediakan olehnya.
Berdasarkan situasi yang kami analisis, kami mendapatkan gambaran seperti itu.
Saat ini, jwt dikembalikan ke front end. Saat ini, permintaan lain yang dibuat oleh front end akan membawa token. Jadi, langkah pertama kita adalah memverifikasi apakah token tersebut dibawa, mengurai token, dan mendapatkan userid yang sesuai , dan merangkumnya sebagai objek Anthentication disimpan di SecurityContextHolder (sehingga filter lain bisa mendapatkannya).
Jadi ada pertanyaan lain di sini. Bagaimana cara mendapatkan informasi pengguna yang lengkap setelah mendapatkan userid dari filter otentikasi jwt?
Di sini kita menggunakan redis. Ketika server mengautentikasi dengan menggunakan ID pengguna untuk menghasilkan jwt ke front end, ID pengguna digunakan sebagai kunci dan informasi pengguna disimpan dalam redis sebagai nilainya dari redis melalui userid.
Dari eksplorasi awal prinsip-prinsip di atas, kami juga telah menganalisis secara kasar apa yang perlu kami lakukan jika kami menerapkan sendiri proses otentikasi pemisahan front-end dan back-end.
Gabung:
a.antarmuka masuk khusus
Panggil metode ProviderManager untuk autentikasi. Jika autentikasi lolos, jwt akan dibuat.
Simpan informasi pengguna di redis
b. Sesuaikan Layanan Detail Pengguna
Kueri database di kelas implementasi ini
memeriksa:
a. Sesuaikan filter otentikasi jwt
Dapat token
Parsing token untuk mendapatkan userid-nya
Dapatkan informasi pengguna lengkap dari redis
Simpan di SecurityContextHolder
Pertama, Anda perlu menambahkan dependensi yang sesuai
- <!-- 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>
Kemudian kita perlu menggunakan Redis dan menambahkan konfigurasi terkait Redis.
Pertama adalah serializer 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);
- }
-
- }
Buat RedisConfig dan buat serializer di dalamnya untuk menyelesaikan masalah seperti karakter yang kacau.
- 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;
- }
- }
Penting juga untuk menyatukan kelas respons
- 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;
- }
- }
Anda memerlukan kelas alat jwt untuk menghasilkan jwt dan mengurai 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();
- }
- }
Tentukan kelas alat Redis lainnya, RedisCache, yang memudahkan kita memanggil redistemplate
- 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);
- }
- }
-
Kita juga dapat menulis data untuk responnya, jadi kita juga memerlukan alat kelas 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;
- }
- }
Terakhir, tulis kelas entitas pengguna yang sesuai
- 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;
- }
-
Berdasarkan analisis kami di atas, kami perlu menyesuaikan UserDetailsService agar SpringSecuriry menggunakan UserDetailsService kami. UserDetailsService kami sendiri dapat menanyakan nama pengguna dan kata sandi dari database.
Pertama-tama kita membuat tabel database 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='用户表';
Kemudian perkenalkan driver myBatisPlus dan 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>
Kemudian konfigurasikan informasi yang relevan dari database.
Kemudian tentukan antarmuka mapper UserMapper dan gunakan mybatisplus untuk menambahkan anotasi yang sesuai.
- package org.example.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import org.example.domain.User;
-
- public interface UserMapper extends BaseMapper<User> {
- }
Kemudian konfigurasikan pemindaian komponen
Terakhir tes apakah mp dapat digunakan secara normal.
Perkenalkan Junit
Dengan cara ini dapat digunakan secara normal.
Selanjutnya kita perlu mengimplementasikan kode inti.
Pertama, mari kita sesuaikan 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);
- }
- }
Di sini pengguna dienkapsulasi sebagai UserDetails dan dikembalikan.
- 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;
- }
- }
Terakhir, ada satu hal di sini, yaitu kita perlu melakukan tes login untuk mendapatkan data dari database. Kita perlu menulis data pengguna ke dalam tabel, dan jika Anda ingin kata sandi pengguna dikirimkan dalam bentuk teks yang jelas , Anda perlu menambahkan {noop} sebelum kata sandi.
Di sini Anda dapat memasukkan nama pengguna dan kata sandi ke dalam database untuk login.
Mari kita bahas mengapa {noop} harus ditambahkan di depan kata sandi, karena PasswordEncoder default memerlukan format kata sandi dalam database menjadi {id}kata sandi. Ini akan menentukan metode enkripsi kata sandi berdasarkan id, tapi kami umumnya tidak menggunakan metode ini, jadi PasswordEncoder perlu diganti.
Selanjutnya kita akan mengujinya dan melihat.
Anda dapat melihat bahwa dua kata sandi asli yang kami berikan di sini sama, tetapi kami mendapatkan hasil yang berbeda. Ini sebenarnya terkait dengan algoritma pengasinan. Saya akan menulis artikel tentang enkripsi khusus nanti.
Setelah mendapatkan kata sandi terenkripsi, Anda dapat menyimpan kata sandi terenkripsi di database. Kemudian Anda dapat masuk dengan memverifikasi kata sandi teks biasa yang dikirimkan dari ujung depan dengan kata sandi terenkripsi di database.
Saat ini, kami memulai proyek untuk masuk, dan menemukan bahwa kami tidak dapat lagi masuk dengan kata sandi sebelumnya, karena database harus menyimpan kata sandi terenkripsi yang disimpan dalam database pada tahap pendaftaran, bukan kata sandi asli (karena saya tidak mendaftar, saya akan mengenkripsi kata sandinya) Kata sandi itu ditulis ke dalam database dengan sendirinya).
Kita perlu mengimplementasikan antarmuka login dan kemudian membiarkan SpringSecuruty mengizinkannya. Jika tidak diizinkan, itu akan bertentangan. Di antarmuka, otentikasi pengguna dilakukan melalui metode otentikasi AuthenticationManager, jadi kita perlu mengkonfigurasi AuthenticationManager yang akan disuntikkan. ke dalam wadah di SecurityConfig.
Jika otentikasi berhasil, jwt perlu dibuat dan dimasukkan ke dalam respons. Agar pengguna dapat mengidentifikasi pengguna tertentu melalui jwt saat membuat permintaan berikutnya, informasi pengguna perlu disimpan di redis, dan pengguna harus disimpan di redis. ID dapat digunakan sebagai kunci.
Tulis LoginController terlebih dahulu
Kemudian tulis Layanan yang sesuai.
Suntikkan AuthenticationManager di SecurityConfig dan lepaskan antarmuka login.
Dalam logika bisnis di layanan, jika otentikasi gagal, pengecualian khusus akan dikembalikan, tetapi jika otentikasi berhasil, bagaimana kita mendapatkan informasi terkait.
Di sini kita dapat melakukan debug dan melihat objek yang diperoleh.
Di sini ditemukan bahwa informasi terkait yang diperlukan dapat diperoleh dari Kepala Sekolah.
Kemudian lengkapi kodenya.
Terakhir, ujilah.
Saya akan menempelkan kodenya terlebih dahulu.
- @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为空的放行
- }
- }
Pertama-tama untuk mendapatkan token disini, kita ambil token yang sesuai dari header permintaan, lalu periksa apakah kosong, jika kosong kita akan langsung melepaskannya tanpa melalui proses lanjutan akan mengurai token, mendapatkan userid di dalamnya, dan kemudian berdasarkan userid. Dapatkan informasi pengguna yang sesuai dari redis, dan terakhir simpan di SecurityContextHolder, karena filter selanjutnya perlu mendapatkan informasi otentikasi harian darinya, dan akhirnya melakukan analisis. operasi.
Hal lain yang perlu diperhatikan adalah SecurityContextHolder.getContext().setAuthentication() perlu meneruskan objek otentikasi. Saat kita membuat objek, kita menggunakan tiga parameter, karena parameter ketiga adalah kunci untuk menentukan apakah akan mengautentikasi atau tidak.
Selanjutnya kita perlu mengkonfigurasi filter ini.
Kemudian ketika kita mengakses antarmuka pengguna/login, badan respons dengan token akan dikembalikan kepada kita. Ketika kita mengakses antarmuka hello lagi, itu akan menjadi 403. Karena tidak membawa token, maka sesuai dengan kode di atas. Jika tidak ada token, isi respons akan dilepaskan dan pengembalian tidak akan menjalankan proses selanjutnya (Pelepasan di sini karena ada filter lain yang secara khusus memberikan pengecualian untuk diproses nanti, dan pengembalian adalah untuk mencegahnya melewatinya. proses respons)
Saat ini, jika kita memasukkan token yang dihasilkan oleh pengguna/login ke dalam header permintaan antarmuka hello, kita dapat mengaksesnya secara normal.
Maka tujuan dari kumpulan filter kami telah tercapai (mendapatkan token, menguraikan token, dan menyimpannya di SecurityContextHolder)
Pada titik ini, lebih mudah bagi kami untuk keluar. Kami hanya perlu menghapus data terkait di redis. Saat kami membawa token untuk akses nanti, informasi pengguna terkait di redis akan diperoleh di filter khusus kami Jika tidak bisa, berarti Anda belum login.
Kami membawa token ini untuk mengakses antarmuka /user/logout.
Kemudian fungsi logout diimplementasikan.
Artikel ini dipelajari dari pembaruan ketiga stasiun b! ! !