2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Sisällysluettelo
2.1 Kirjautumisen vahvistusprosessi
2.2 Periaatteen alustava selvitys
2.3.3.1 Tietokannan vahvistuskäyttäjä
2.3.3.2 Salasanasalattu tallennustila
Spring Security on Spring-perheen turvallisuuden hallintakehys verrattuna Shiroon, toiseen tietoturvakehykseen, se tarjoaa rikkaampia toimintoja ja rikkaampia yhteisöresursseja kuin Shiro.
Yleisesti ottaen Spring Securityä käytetään yleisemmin suurissa projekteissa ja Shiroa käytetään yleisemmin pienissä projekteissa, koska Spring Securityyn verrattuna Shiro on helpompi aloittaa.
Yleiset verkkosovellukset tarvitsevatSertifiointijaValtuuta。
Todennus ja valtuutus ovat Spring Securityn ydintoimintoja tietoturvakehyksenä!
Rakennataan ensin SpringBoot-projekti.
Tällä hetkellä käytämme yksinkertaista hello-käyttöliittymää, jonka kirjoitimme varmistaaksemme, onko rakentaminen onnistunut.
Esittele sitten SpringSecurity.
Katsotaanpa tällä kertaa käyttöliittymän vaikutusta.
Kun SpringSecurity on otettu käyttöön, käyttöliittymä siirtyy automaattisesti sisäänkirjautumissivulle. Oletuskäyttäjätunnus on käyttäjä, ja salasana lähetetään konsoliin.
Ensinnäkin meidän on ymmärrettävä sisäänkirjautumisen varmistusprosessi. Ensinnäkin käyttöliittymä sisältää käyttäjänimen ja salasanan kirjautumisliittymään pääsemiseksi. Jos käyttäjätunnusta/käyttäjätunnusta käytetään oikein, jwt luodaan ja sitten Vastaa jwt käyttöliittymään, ja sitten pääsy muihin pyyntöihin kirjautumisen jälkeen kuljettaa tunnuksen pyynnön otsikossa Aina, kun palvelin hankkii tunnuksen jäsennyspyynnön otsikko, hankkii käyttäjätunnuksen ja hankkii käyttäjätietoa ja katsojan käyttöoikeudet käyttäjätunnuksen perusteella, jos sinulla on lupa, vastaa käyttöliittymään.
SpringSecurityn periaate on itse asiassa suodatinketju, joka tarjoaa suodattimille erilaisia toimintoja.
Debugin avulla voimme myös nähdä, mitkä suodattimet ovat SpringSecurity-suodatinketjussa nykyisessä järjestelmässä ja niiden järjestyksen.
Katsotaan seuraavaksi todennuksen vuokaavion analyysiä.
Tässä meidän tarvitsee vain ymmärtää prosessi Yksinkertaisesti sanottuna:
Käyttäjä lähetti käyttäjänimen ja salasanan, UsernamePasswordAuthenticationFilter kapseloi sen Authentication-objektiksi ja kutsuu todennusmenetelmän DaoAuthenticationProvider-todennusta varten ja kutsuu sitten loadUserByUserName-menetelmän käyttäjälle on etsiä muistista ja kapseloida vastaavat käyttäjätiedot UserDetails-objektiin, vertailla UserDetails-salasanaa ja Authentication-salasanaa, jos se on oikein, aseta UserDetailsin käyttöoikeustiedot Authentication-objektiin , palauta sitten Authentication-objekti ja lopuksi käytä SecurityContextHolder.getContext( ).setAuthentication-menetelmä tallentaa tämän objektin, ja muut suodattimet saavat nykyiset käyttäjätiedot SecurityContextHoderin kautta. (Sinun ei tarvitse opetella ulkoa tätä kappaletta ymmärtääkseen sen)
Sitten tunnemme prosessin ennen kuin voimme muokata sitä. Ensinnäkin, kun etsimme muistista, meidän on haettava tietokannasta (tässä meidän on mukautettava UserDetailsService-toteutusluokka), emmekä käytä oletusarvoista Käyttäjätunnusta ja salasanaa. , ja kirjautumisliittymän tulee olla itse kirjoittama, eikä hänen toimittamaa oletuskirjautumissivua tarvitse käyttää.
Analysoimamme tilanteen perusteella voimme saada tällaisen kuvan.
Tällä hetkellä jwt palautetaan käyttöliittymään, ja muut käyttöliittymän tekemät pyynnöt kuljettavat tokenin. Joten ensimmäinen askel on tarkistaa, onko merkintä mukana, jäsentää token, hankkia vastaava käyttäjätunnus ja kapseloida. se on Anthentication-objekti tallennetaan SecurityContextHolderiin (jotta muut suodattimet voivat saada sen).
Joten tässä on toinen kysymys, kuinka saada täydelliset käyttäjätiedot sen jälkeen, kun käyttäjätunnus on saatu jwt-todennussuodattimesta?
Tässä käytetään redis:tä, kun palvelin todentaa käyttäjätunnuksen avulla jwt:n käyttöliittymään, käyttäjätunnusta käytetään avaimena ja käyttäjän tiedot tallennetaan arvona redisistä käyttäjätunnuksen kautta.
Yllä olevien periaatteiden alustavan selvityksen perusteella olemme myös karkeasti analysoineet, mitä meidän tulee tehdä, jos toteutamme etupään ja takapään erottelun autentikointiprosessin itse.
Kirjaudu sisään:
a. Mukautettu kirjautumisliittymä
Kutsu ProviderManager-menetelmä todennusta varten. Jos todennus onnistuu, jwt luodaan.
Tallenna käyttäjätiedot redisiin
b. Mukauta UserDetailsService
Tee kysely tämän toteutusluokan tietokannasta
tarkistaa:
a. Mukauta jwt-todennussuodatinta
Hanki tunnus
Jäsennä tunnus saadaksesi sen käyttäjätunnuksen
Hanki täydelliset käyttäjätiedot redisiltä
Tallenna SecurityContextHolderissa
Ensin sinun on lisättävä vastaavat riippuvuudet
- <!-- 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>
Sitten meidän on käytettävä Redistä ja lisättävä Redikseen liittyvät kokoonpanot.
Ensimmäinen on FastJsonin serialisointi
- 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);
- }
-
- }
Luo RedisConfig ja luo siihen serialisoija ongelmien, kuten sotkuisten merkkien, ratkaisemiseksi.
- 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;
- }
- }
On myös tarpeen yhtenäistää vastausluokka
- 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;
- }
- }
Tarvitset jwt-työkaluluokat jwt:n luomiseen ja jwt:n jäsentämiseen.
- 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();
- }
- }
Määrittele toinen Redis-työkaluluokka RedisCache, jonka avulla meidän on helpompi kutsua redistemplatea
- 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);
- }
- }
-
Saatamme myös kirjoittaa tietoja vastaukseen, joten tarvitsemme myös työkaluluokan 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;
- }
- }
Kirjoita lopuksi vastaava käyttäjäentiteettiluokka
- 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;
- }
-
Yllä olevan analyysimme mukaan meidän on mukautettava UserDetailsService, jotta SpringSecuriry voi käyttää UserDetailsServiceä. Oma UserDetailsServicemme voi tiedustella käyttäjätunnusta ja salasanaa tietokannasta.
Luomme ensin tietokantataulukon 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='用户表';
Esittele sitten myBatisPlus- ja mysql-ajurit.
- <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>
Määritä sitten tietokannan olennaiset tiedot.
Määritä sitten kartoittajan käyttöliittymä UserMapper ja lisää vastaavat huomautukset mybatisplus-ohjelmalla.
- package org.example.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import org.example.domain.User;
-
- public interface UserMapper extends BaseMapper<User> {
- }
Määritä sitten komponenttien skannaus
Lopuksi testaa, voiko mp:tä käyttää normaalisti.
Esittele junit
Näin sitä voidaan käyttää normaalisti.
Seuraavaksi meidän on otettava käyttöön ydinkoodi.
Ensin mukautetaan 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);
- }
- }
Tässä käyttäjä kapseloidaan UserDetails-muodossa ja palautetaan.
- 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;
- }
- }
Lopuksi tässä on asia, eli meidän on suoritettava sisäänkirjautumistesti saadaksemme tietoja tietokannasta. Meidän on kirjoitettava käyttäjätiedot taulukkoon, ja jos haluat, että käyttäjän salasana lähetetään selkeänä tekstinä , sinun on lisättävä {noop} ennen salasanaa.
Täällä voit syöttää käyttäjätunnuksen ja salasanan tietokantaan kirjautuaksesi sisään.
Puhutaanpa siitä, miksi salasanan eteen lisätään {noop}, koska oletusarvoinen PasswordEncoder vaatii tietokannan salasanamuodon olevan {id}salasana. Se määrittää salasanan salausmenetelmän tunnuksen perusteella, mutta me yleensä Älä käytä tätä menetelmää, joten PasswordEncoder on vaihdettava.
Seuraavaksi testataan ja katsotaan.
Näet, että tässä antamamme kaksi alkuperäistä salasanaa ovat samat, mutta saimme erilaisia tuloksia. Tämä liittyy itse asiassa suolausalgoritmiin. Kirjoitan myös artikkelin mukautetusta salauksesta.
Kun olet saanut salatun salasanan, voit tallentaa salatun salasanan tietokantaan. Tämän jälkeen voit kirjautua sisään tarkistamalla käyttöliittymästä välitetyn selkeän salasanan tietokannassa olevalla salatulla salasanalla.
Tässä vaiheessa aloitimme sisäänkirjautumisprojektin ja huomasimme, että emme voi enää kirjautua sisään aiemmalla salasanalla, koska tietokantaan pitäisi tallentaa rekisteröitymisvaiheessa tietokantaan tallennettu salattu salasana, ei alkuperäinen salasana (koska minä ei rekisteröitynyt, salaan salasanan) Salasana kirjoitetaan tietokantaan itsestään).
Meidän on otettava käyttöön kirjautumisrajapinta ja annettava SpringSecurutyn sallia se. Jos se ei ole sallittua, se on ristiriitainen. SecurityConfigin säilöön.
Jos todennus onnistuu, jwt on luotava ja lisättävä vastaukseen, jotta käyttäjä voi tunnistaa tietyn käyttäjän jwt:n kautta seuraavan pyynnön tehdessään, käyttäjätiedot on tallennettava redikseen ja käyttäjä. ID:tä voidaan käyttää avaimena.
Kirjoita ensin LoginController
Kirjoita sitten vastaava palvelu.
Lisää AuthenticationManager SecurityConfigiin ja vapauta kirjautumisliittymä.
Palvelun liiketoimintalogiikassa jos todennus epäonnistuu, palautetaan mukautettu poikkeus, mutta jos todennus onnistuu, miten saamme vastaavat tiedot.
Täällä voimme tehdä virheenkorjauksen ja nähdä saadut objektit.
Tästä selviää, että vastaavat vaaditut tiedot ovat saatavissa Rehtorilta.
Täytä sitten koodi.
Lopuksi testaa sitä.
Liitän koodin ensin.
- @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为空的放行
- }
- }
Ensinnäkin, saadaksemme tunnuksen tänne, saamme vastaavan tunnuksen pyynnön otsikosta ja tarkistamme sen olevan tyhjä. Jos se on tyhjä, vapautamme sen suoraan ilman seurantaprosessia jäsentää tunnuksen, saa käyttäjätunnuksen sisään ja sitten käyttäjätunnuksen perusteella hankkii vastaavat käyttäjätiedot redisistä ja tallentaa ne lopuksi SecurityContextHolderiin, koska myöhempien suodattimien on hankittava siitä päivittäiset todennustiedot ja suoritettava lopuksi analyysi. toiminnot.
Toinen huomioitava seikka on se, että SecurityContextHolder.getContext().setAuthentication():n on läpäistävä todennusobjekti.
Seuraavaksi meidän on määritettävä tämä suodatin.
Sitten kun käytämme käyttäjä/kirjautumisliittymää, meille palautetaan vastausteksti, jossa on token. Kun siirrymme uudelleen hello-käyttöliittymään, se on 403. Koska se ei sisällä merkkiä, se vastaa yllä olevaa koodia. Ilman merkkiä vastauksen runko vapautetaan, eikä palautus suorita seuraavaa prosessia (vapautus johtuu siitä, että on olemassa muita suodattimia, jotka antavat poikkeuksia myöhempää käsittelyä varten, ja palautuksen tarkoituksena on estää sitä kulkemasta vastauksen läpi. käsitellä asiaa)
Tällä hetkellä, jos laitamme käyttäjän/sisäänkirjautumisen luoman tunnuksen hello-käyttöliittymän pyyntöotsikkoon, voimme käyttää sitä normaalisti.
Sitten suodatinsarjamme tarkoitus on saavutettu (tokenien hankkiminen, tunnisteiden jäsentäminen ja niiden tallentaminen SecurityContextHolderiin)
Tässä vaiheessa meidän on helpompi kirjautua ulos Redis-sovelluksesta et saa sitä, se tarkoittaa, että et ole kirjautunut sisään.
Käytämme tätä merkkiä käyttääksemme /user/logout-käyttöliittymää.
Sitten uloskirjautumistoiminto toteutetaan.
Tämä artikkeli opittiin aseman b kolmannesta päivityksestä! ! !