序文
先ほど書い たチュチュ劇場管理システムのデモは、シロ認証に基づいています。プロジェクトのフロントエンドとバックエンドを分離した後は、Spring Securityを統合する方が明らかに便利です。結局のところ、Springが使用され、権限管理はもちろん、SpringSecurity。
私は半日かけてメモを整理しました。それがあなたのお役に立てば幸いです。
Spring Securityの1文の概要:一連のフィルターチェーンで構成されるアクセス許可認証。
1.依存関係を追加します
環境:プロジェクトはSpringInitializrを使用してSpringBootをすばやくビルドし、バージョンはspring-boot-starter-parentによって管理され ます 。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
依存関係を追加した後、プロジェクトを開始して、以下を参照してください。
1.1コンソール印刷
次の図に示すように、コンソールはパスワードの文字列を出力します。
プロジェクトの方法の1つにアクセスしてください。
http://localhost:7777/tmax/videoCategory/getAll
奇妙なことに、なぜ/ loginパスにジャンプして、ログインさせたのですか?
1.2アカウントログイン
ログインフォームに次のように入力します。
- ユーザー名:user
- パスワード:0839a4ba-c8a3-4aee-8a6e-cd19c1d0b0c1(コンソールに印刷)
[サインイン]をクリックして、ターゲットアドレスにジャンプします。
Spring Securityの依存関係を追加した後、2つのことが実際にトリガーされました。システム内のすべての接続サービスはしばらくの間保護され、その後、デフォルトの構成フォーム認証が行われました。
2.基本原則
SpringSecurityの全体的なワークフローは次のとおりです。
緑の認証方法を設定でき、オレンジと青の位置を変更することはできません。
セキュリティには2つの認証方法があります。
- httpbasic
- 上記のデフォルトのformLogin
同様に、セキュリティには2つのフィルタクラスもあります。
- UsernamePasswordAuthenticationFilterは、フォームログインフィルターを表します
- BasicAuthenticationFilterは、httpbaicログインフィルターを意味します
図のオレンジ色のFilterSecurityInterceptorは、現在のリクエストがコントローラーにアクセスできるかどうかを決定する最後のフィルターであり、判断ルールがこれに配置されます。
失敗すると、このフィルターの前にあるExceptionTranslationFilterフィルターに例外がスローされます。
ExceptionTranslationFilterが例外情報を受信すると、上記のユーザーログインインターフェイスに示されているように、ユーザーが認証するようにガイドするページにジャンプします。
3つのカスタム認証ロジック
実際の開発では、上記のSpringSecurityのデフォルトの方法を使用することはできません。SpringSecurityのデフォルト構成をオーバーライドするにはどうすればよいですか。
例として、デフォルトのフォーム認証方法をhttpbasicに変更します。
SpringSecurityカスタム構成クラスを作成します:WebSecurityConfig.java
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
registry.and()
表单登录方式
.formLogin()
.permitAll()
.and()
.logout()
.permitAll()
.and()
.authorizeRequests()
任何请求
.anyRequest()
需要身份认证
.authenticated()
.and()
关闭跨站请求防护
.csrf().disable()
前后端分离采用JWT 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
}
プロジェクトを再起動し、変更されたhttpbasic認証を確認しました。
ここでも、ユーザーが指定したデフォルトのユーザー名を使用し、サーバーが起動するたびにパスワードが自動的に生成されます。認証ロジックをカスタマイズできますか?たとえば、データベースでユーザーログインを使用しますか?
答えはイエスです。
カスタムユーザー認証ロジックには、次の3つの手順が必要です。
- ユーザー情報取得ロジックの処理
- ユーザー検証ロジックの処理
- パスワードの暗号化と復号化を処理する
次に、次の3つの手順を見て、カスタムログインを実装しましょう。
3.1ユーザー情報取得ロジックの処理
Spring Securityのユーザー情報取得ロジックの取得ロジックは、インターフェースUserDetailServiceにカプセル化されています。コードは次のとおりです。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
このインターフェースには、String型のユーザー名パラメーターを受け取り、UserDetailsオブジェクトを返すloadUserByUsername()という1つのメソッドしかありません。
では、この方法は正確に何をするのでしょうか?
フロントエンドユーザーが入力したユーザー名を渡し、データベースストレージに移動して対応するユーザー情報を取得し、それをUserDetail実装クラスにカプセル化します。
UserDetail実装クラスにカプセル化されて返されると、Spring Srcurityは検証のためにユーザー情報を取得します。検証に合格すると、ユーザーはセッションに参加します。それ以外の場合は、UsernameNotFoundException例外がスローされ、SpringSecurityは対応する応答を行います。プロンプト情報をキャプチャした後。
ユーザー情報取得ロジックを処理するには、UserDetailsServiceを自分で実装する必要があります
新しいUserDetailsServiceImpl.java
@Slf4j
@Component
public class UserDetailsServiceImpl implements UserDetailsService{
@Autowired
private UserService userService;
/**
* 从数据库中获取用户信息,返回一个 UserDetails 对象,
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
通过用户名获取用户
User user = userService.findByUsername(username);
将 user 对象转化为 UserDetails 对象
return new SecurityUserDetails(user);
}
}
SecurityUserDetail.java
public class SecurityUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
public SecurityUserDetails(User user) {
if(user!=null) {
this.setUsername(user.getUsername());
this.setPassword(user.getPassword());
this.setStatus(user.getStatus());
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
理想型返回 admin 权限,可自已处理这块
return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
}
/**
* 账户是否过期
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 是否禁用
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否过期
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否启用
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
この時点で、ユーザー情報取得の処理のロジック部分が完了し、主にUserDetailsServiceインターフェイスのloadUserBynameメソッドが実装されます。
SecurityUserDetailクラスが変換に使用されるのはなぜですか?
実際には、Userオブジェクトを直接返すことができますが、Userオブジェクトを直接返す場合は、セキュリティパッケージのユーザーが返されることに注意してください。
これが行われる理由については、セキュリティパッケージのユーザーが返されると、ローカルデータベースを使用する意味が失われます。カスタムログインロジックについては、以下で詳しく説明します。
ログインして再試行してみましょう。
その中で、niceyooはデータベースのユーザー情報であり、次の図は成功したジャンプを示しています。
3.2ユーザー検証ロジックの処理
ユーザーの検証ロジックには、主に2つの側面が含まれます。
- パスワードが一致するかどうか[SprinSecurityによって処理され、パスワードを教えてください]
- パスワードの有効期限が切れているかどうか、アカウントが凍結されているかどうかなど。
前者は、UserDetailsServiceのloadUserByname()メソッドを実装することで実装されています。次に、後者を見てみましょう。
ユーザーパスワードの有効期限が切れているかどうか、パスワードが凍結されているかどうかなどは、UserDetailsインターフェイスを実装する必要があります。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();授权列表;
String getPassword();从数据库中查询到的密码;
String getUsername();用户输入的用户名;
boolean isAccountNonExpired();当前账户是否过期;
boolean isAccountNonLocked();账户是否被锁定;
boolean isCredentialsNonExpired();账户的认证时间是否过期;
boolean isEnabled();是账户是否有效。
}
主に最後の4つの方法を見てください。
1. IsAccountNonExpired()アカウントの有効期限が切れていない場合は、trueを返し、有効期限が切れていないことを示します
2. IsAccountNonLocked()アカウントはロックされていません
3. IsCredentialsNonExpired()パスワードの有効期限が切れています
4. IsEnabled()が削除されます
上記の4つの方法はすべて、実際の状況に応じて対応できます。
3.3パスワードの暗号化と復号化の処理
WebSecurityConfigカスタム構成クラスに戻ります。参加:
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());//加密
}
このconfigureメソッドを構成した後、フロントエンドから渡されるパスワードは暗号化されるため、データベースから照会されるパスワードは暗号化する必要があり、このプロセスはユーザーが登録するときにすべて暗号化されます。
補足:UserDetailsServiceImplは、カスタムUserDetailsService実装クラスです。
4つのパーソナライズされた認証プロセス
同様に、実際の開発では、ユーザーログイン認証にSpring Security独自のメソッドやページを使用することは不可能であり、プロジェクトに適したログインプロセスをカスタマイズする必要があります。
Spring Securityは、ユーザーが構成ファイルで独自のログインページを構成することをサポートします。ユーザーが構成する場合は、ユーザー自身のページが使用されます。それ以外の場合は、モジュールの組み込みログインページが使用されます。
WebSecurityConfig構成クラスに成功および失敗フィルターを追加しました。
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailHandler failHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
registry.and()
表单登录方式
.formLogin()
.permitAll()
成功处理类
.successHandler(successHandler)
失败
.failureHandler(failHandler)
.and()
.logout()
.permitAll()
.and()
.authorizeRequests()
任何请求
.anyRequest()
需要身份认证
.authenticated()
.and()
关闭跨站请求防护
.csrf().disable()
前后端分离采用JWT 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
AuthenticationSuccessHandlerとAuthenticationFailHandlerを追加すると、パッケージが自動的にガイドされますが、これはパーソナライズされた認証プロセスであるため、当然、自分で実装する必要があります〜
では、どのような効果を達成したいのでしょうか。
カスタムログインの正常な処理:
カスタムログイン失敗の処理:
なぜこの新しいスタイルのリターンを採用するのですか?
ユーザーが正常にログインした後、Spring Securityのデフォルトの処理方法は、元のリンクにジャンプすることです。これは、エンタープライズレベルの開発の一般的な方法でもありますが、Ajaxによって送信されたリクエストが使用されることがあり、Jsonデータが返されることがよくあります。図に示すように:ログインに成功すると、トークンはフォアグラウンドに返され、失敗すると失敗情報が返されます。
AuthenticationSuccessHandler:
Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String username = ((UserDetails)authentication.getPrincipal()).getUsername();
List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails)authentication.getPrincipal()).getAuthorities();
List<String> list = new ArrayList<>();
for(GrantedAuthority g : authorities){
list.add(g.getAuthority());
}
登陆成功生成token
String token = UUID.randomUUID().toString().replace("-", "");
token 需要保存至服务器一份,实现方式:redis or jwt
输出到浏览器
ResponseUtil.out(response, ResponseUtil.resultMap(true,200,"登录成功", token));
}
}
SavedRequestAwareAuthenticationSuccessHandlerはSpringSecurityのデフォルトの成功ハンドラーであり、デフォルトのメソッドはジャンプです。ここでは、認証情報がJsonデータとして返され、他のデータも返される可能性があります。これはビジネス要件に応じて決定されます。たとえば、上記のコードは、ユーザーが正常にログインした後にトークンを返します。このトークンは注意が必要です。サーバーにバックアップする必要があります1つは、結局のところ、次のID検証に使用する必要があります〜
AuthenticationFailHandler:
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
## 默认情况下,不管你是用户名不存在,密码错误,SS 都会报出 Bad credentials 异常信息
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"用户名或密码错误"));
} else if (e instanceof DisabledException) {
ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"账户被禁用,请联系管理员"));
} else {
ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"登录失败,其他内部错误"));
}
}
}
失敗ハンドラーは、成功処理と同じです。
ResponseUtil:
@Slf4j
public class ResponseUtil {
/**
* 使用response输出JSON
* @param response
* @param resultMap
*/
public static void out(HttpServletResponse response, Map<String, Object> resultMap){
ServletOutputStream out = null;
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
out = response.getOutputStream();
out.write(new Gson().toJson(resultMap).getBytes());
} catch (Exception e) {
log.error(e + "输出JSON出错");
} finally{
if(out!=null){
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
gson依存関係を使用するもの:
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
やっと
次の記事では、jwtを統合してユーザーID認証を実現します。