Web サイトの実際のアプリケーション プロセスでは、Web サイトのログイン インターフェイスがロボットによって簡単に使用されないようにし、無意味なユーザー データを生成することを防ぐために、認証コードを使用してある程度傍受します. もちろん、私たちはまだ組み合わせを使用しています.写真認証コードの形式で、より複雑なデジタル計算型の写真認証コードについては後で説明しますので、引き続き私のブログに注目してください。
記事ディレクトリ
実装のアイデア
ブロガー環境: springboot3、java17、thymeleaf
-
ログインページにアクセス
-
ログイン
- キャプチャを確認する
- アカウントとパスワードを確認する
- 検証が成功すると、ログイン資格情報が生成され、クライアントに発行されます
- 検証に失敗した場合は、ログイン情報に戻り、元の入力情報を保持します
-
やめる
- ログイン資格情報を無効な状態に変更する
- ホームページへジャンプ
ログインページへのアクセス方法は上記で説明したので、ここでは詳しく説明しませんが、コードを示しましょう。
// 登录页面
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String getLoginPage() {
return "/site/login";
}
ログインページにアクセスしたら、情報を入力する必要がありますが、認証コードの情報はまだ正しく表示されていないので、次は認証コードの部分を先に実装しましょう。
2 つのデータ テーブルに必要な SQL コードは次のとおりです。
注: 登録プロセスは、以前の記事で確認できます。この記事では、登録済みのアカウント コードをメールでアクティブ化する方法を説明します - yumuing のブログ - CSDN ブログ
-- user表
DROP TABLE IF EXISTS `user`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`salt` varchar(50) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`type` int(11) DEFAULT NULL COMMENT '0-普通用户; 1-超级管理员; 2-版主;',
`status` int(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',
`activation_code` varchar(100) DEFAULT NULL,
`header_url` varchar(200) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_username` (`username`(20)),
KEY `index_email` (`email`(20))
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
-- 登录凭证表
DROP TABLE IF EXISTS `login_ticket`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `login_ticket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`ticket` varchar(45) NOT NULL,
`status` int(11) DEFAULT '0' COMMENT '1-有效; 0-无效;',
`expired` timestamp NOT NULL,
PRIMARY KEY (`id`),
KEY `index_ticket` (`ticket`(20))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Kaptcha 検証コードの設計と検証
現在、Kaptcha は最も広く使用されている画像検証コードです. バージョンは 2.3.2 しかありません. springboot 3 環境では、このプラグイン パッケージで使用されるほとんどの http パッケージを にインポートできないことに注意してください. javax パッケージ。ただし、jakarta パッケージにインポートする必要があります。
次の効果を実現できます: 干渉のある水模様、干渉のない魚眼、干渉のない水模様、干渉のない影、干渉のある影
その中で、テキスト コンテンツの制限、背景画像、テキストの色、サイズ、干渉スタイルの色、全体 (画像) の高さ、幅、画像のレンダリング効果、および干渉の有無をすべてカスタマイズできます。必要に応じて、対応する構成を構成するだけです。もちろん、デフォルトでは springboot に統合されていないため、次のように、対応する依存関係を使用する前にインポートする必要があります。
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
パッケージを正常にインポートしたら、必要に応じて構成クラスを設定する必要があります。関連する構成プロパティは次のとおりです。
構成クラス テンプレートは次のとおりです。
package top.yumuing.community.config;
import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptchaProduce(){
Properties properties=new Properties();
//图片的宽度
properties.setProperty("kaptcha.image.width","100");
//图片的高度
properties.setProperty("kaptcha.image.height","40");
//字体大小
properties.setProperty("kaptcha.textproducer.font.size","32");
//字体颜色(RGB)
properties.setProperty("kaptcha.textproducer.font.color","0,0,0");
//验证码字符的集合
properties.setProperty("kaptcha.textproducer.char.string","123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
//验证码长度(即在上面集合中随机选取几位作为验证码)
properties.setProperty("kaptcha.textproducer.char.length","4");
//图片的干扰样式:默认存在无规则划线干扰
//无干扰:com.google.code.kaptcha.impl.NoNoise
properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");
//图片干扰颜色:默认为黑色
properties.setProperty("kaptcha.noise.color", "black");
//图片渲染效果:默认水纹
// 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
//properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");
DefaultKaptcha Kaptcha = new DefaultKaptcha();
Config config=new Config(properties);
Kaptcha.setConfig(config);
return Kaptcha;
}
}
関連するプロパティを構成した後、検証コード生成用のインターフェイスを開発できます. まず、Producer が管理のために Bean ファクトリに入るようにし、次に検証コード テキストを生成して、後続の検証コード検証のためにセッションに渡します.対応する検証コード画像を生成し、BufferedImage の形式で保存し、HttpServletResponse と ImageIO を使用して画像をブラウザに転送します.その中で、画像の戻り型の設定に注意してください.手動で閉じる必要はありません. IO ストリーム. Springboot はそれを管理して、自己閉鎖を実現します。このとき、Get メソッドでドメイン名/imageCode にアクセスすると、対応する認証コードの画像が返ってきます。
//验证码
@RequestMapping(path = "/imageCode",method = RequestMethod.GET)
public void getImgCode(HttpServletResponse response, HttpSession session){
String codeText = imageCodeProducer.createText();
BufferedImage imageCode = imageCodeProducer.createImage(codeText);
// 将验证码文本存入 session
session.setAttribute("imageCode", codeText);
//设置返回类型
response.setContentType("image/jpg");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(imageCode, "jpg", os);
} catch (IOException e) {
logger.error("响应验证码失败!"+e.getMessage());
}
}
もちろん, ユーザーアクセストラフィックを節約するために, ブラウザの中には, 取得した静的リソースリンクへのアクセスを自動的にインテリジェントに停止するものもあります. したがって, ブラウザの適応を完了するには, 追加のパラメータを追加する必要があります. ここでは, JavaScriptを使用して各アクセス検証コードを変換します. number パラメータを画像リンクに追加して、インテリジェントなトラフィックの節約を確保します。もちろん、このパラメーターを取得するためにコントローラーに行く必要はありません。これは無意味であり、すべてのパラメーターが一致する必要はないためです。コードは以下のように表示されます:
function refresh_imageCode() {
var path = "/imageCode?p=" + Math.random();
$("#imageCode").attr("src", path);
}
確認コードを取得したら、それを校正する必要があります.確認コードが渡された後にのみ、アカウントとパスワードを確認できます. 検証コードの校正で最も重要な点は、大文字を無視する必要があり、ユーザーの忍耐を要求できないことです。検証コードが失敗したことを検証するときは、送信者の検証コード テキストが空であるか、テキストが矛盾していることによって引き起こされるエラーを考慮する必要があるだけでなく、受信者 (サーバー) の検証コード テキストがそれを防ぐために保存されているかどうかも考慮する必要があります。ツールは、このインターフェイスによって生成された空のデータに直接ポスト アクセスします。コードは以下のように表示されます:
//登录
@RequestMapping(path = "/login",method = RequestMethod.POST)
public String login(String username, String password, String code,
boolean rememberMe, Model model, HttpSession session, HttpServletResponse response){
String imageCode = (String) session.getAttribute("imageCode");
// 验证码
if (StringUtils.isBlank(imageCode) || StringUtils.isBlank(code) || !imageCode.equalsIgnoreCase(code)){
model.addAttribute("codeMsg","验证码不正确!");
return "/site/login";
}
}
リメンバーミー機能の実装
ユーザーがログインするときに、ボタンを覚えているかどうかを確認する必要があることがよくあります.これは、ユーザーが頻繁にログインしてユーザー数を失うことなく、アプリケーションを長く使用できるようにするためです. もちろん、一部のユーザーは、ユーザー資格情報を長期間保存することを望まず、頻繁な更新によってユーザー データのセキュリティをある程度確保したいと考えています。この機能を実現するのは難しくありませんが、データを送信するときにもう 1 つブール型のパラメーターを追加するだけです。コードを読みやすくするために、次の 2 つの定数が追加されています: login default status timeout time constant, remember me login status timeout time constant.
// 默认登录状态超时常量
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
// 记住状态的登录凭证超时时间
int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
後は、ログイン インターフェイスで判断するだけです.Boolean 値が true であることを思い出してください。コードは次のようになります。
// 是否记住我
int expiredSeconds = rememberMe ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
アカウントとパスワードを確認する
標準プロセスに従って、最初にデータ アクセス レイヤーから書き込みを開始します. アカウントとパスワードを確認するために、クエリ ステートメントを使用する必要があります. もちろん、1 つのクエリ ステートメントで十分です. 2 つのパラメーターに対して 2 つのクエリ ステートメントを作成する必要はありません.このオブジェクトについては、マッピング メソッドで get メソッドを直接使用して、必要な検証作業を実行できます。ここでは、ユーザー オブジェクトを取得するために、パラメーターとしてユーザー名を指定したクエリ ステートメントが使用されます。具体的なコードは次のとおりです。
userMapper.java
User selectOneByUsername(@Param("username") String username);
userMapper.xml
<sql id="Base_Column_List">
id,username,password,
salt,email,type,
status,activation_code,header_url,
create_time
</sql>
<select id="selectOneByUsername" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
where
username = #{username,jdbcType=VARCHAR}
</select>
このクエリ ステートメントを使用する前に、渡されたアカウントとパスワードが空であってはならないことを最初に確認する必要があります。これにより、クエリが意味のあるものになります。ユーザー オブジェクトを取得した後、まずアカウントが存在するかどうかを確認します。存在しない場合は、エラーメッセージ. 存在する場合, そのアカウントステータスがアクティブ化されているかどうかを確認します. 存在しない場合, エラーメッセージを返します. 存在する場合, 検証作業を実行できます. もちろん, アカウントが存在する場合, ユーザー名は必要ありません.検証済み、パスワードのみを検証する必要があります。コードは以下のように表示されます:
//空值处理
if(StringUtils.isBlank(username)){
map.put("usernameMsg", "账号不能为空!");
return map;
}
if (StringUtils.isBlank(password)){
map.put("passwordMsg", "密码不能为空!");
return map;
}
//验证账号
User user = userMapper.selectOneByUsername(username);
if (user == null){
map.put("usernameMsg","该账号不存在");
return map;
}
//验证状态
if (user.getStatus() == 0){
map.put("usernameMsg","该账号未激活!");
return map;
}
//验证密码
password = CommunityUtil.md5(password+user.getSalt());
if(!user.getPassword().equals(password)){
map.put("passwordMsg","密码不正确!");
return map;
}
アカウント パスワードの検証が成功したら、ログイン資格情報を Cookie に保存し、グローバル アベイラビリティと有効期限を設定するだけで、ログイン資格情報の有効期限が設定されている限り、後続のクライアントは自動的にその時刻に到着し、ログアウトします。ログイン資格情報を入力して、ログイン ステータスをキャンセルします。検証が失敗した場合は、検証情報が直接返されます。ログインインターフェイスで呼び出すことができます
// 检测账号密码
Map<String,Object> map = userServiceImpl.login(username,password,expiredSeconds);
if (map.containsKey("loginTicket")){
//设置cookie
Cookie cookie = new Cookie("loginTicket",map.get("loginTicket").toString());
cookie.setPath("/");
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
}else {
model.addAttribute("usernameMsg",map.get("usernameMsg"));
model.addAttribute("passwordMsg",map.get("passwordMsg"));
return "/site/login";
}
ログイン認証情報を生成する
最初にデータ アクセス レイヤーから始めましょう。自動インクリメント ID の生成に注意してください。具体的な xml ステートメントは次のとおりです。
<insert id="insertAll" parameterType="LoginTicket" keyProperty="id">
insert into login_ticket
(id, user_id, ticket,
status, expired)
values (#{id,jdbcType=NUMERIC}, #{userId,jdbcType=NUMERIC}, #{ticket,jdbcType=VARCHAR},
#{status,jdbcType=NUMERIC}, #{expired,jdbcType=TIMESTAMP})
</insert>
これは、java.util.UUID を使用して生成される、文字と数字が混在するランダムな文字列の形式です。set メソッドを使用して必要なパラメータをオブジェクトに格納し、対応する insert ステートメントを使用してデータベースに挿入します。デフォルトの有効な状態は 1 であることに注意してください。ログイン認証情報を生成するための特定のログイン インターフェイス コードは次のとおりです。
//生成登录凭证
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());
loginTicket.setStatus(1);
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
loginTicketMapper.insertAll(loginTicket);
map.put("loginTicket",loginTicket.getTicket());
return map;
問題に気付いたかどうかはわかりませんが、有効期限が切れてもステータスは有効です。ログイン資格情報の有効なステータスは、その後のログイン情報の表示の鍵となります.時間の経過後に有効なステータスを自動的に変更する方法も検討します. または、有効期限が切れてもステータスが有効のままになっている問題を変更せずに解決する方法. 引き続きブロガーに注意を払い、後で回答してください.
ログイン認証情報をクライアントに送信すると、基本的にログインの実装が完了します。
関連するコード リソースがアップロードされました。参照:プロジェクト コード
関連するバグ
インターフェイス javax.servlet.http.HttpServletResponse のプライマリまたは単一の一意のコンストラクタが見つかりません
springboot3 は javax.servlet.http パッケージをインポートできません。jakarta.servlet.http をインポートする必要があります
つまり、http パッケージが再び変更されました。
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
ガイドできません。そうしないと、エラーが発生します。
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;