SpringBoot シリーズ: Spring セキュリティの統合

著者のプラットフォーム:

| CSDN:blog.csdn.net/qq_41153943

| ナゲッツ: juejin.cn/user/651387…

| 志湖: www.zhihu.com/people/1024…

| GitHub: github.com/JiangXia-10…

| WeChat 公開アカウント: 1024 メモ

この記事は約 2,230 ワードであり、読むのにかかる予想時間は 15 分です

1. Spring Security の概要

1. フレームワークの紹介

Spring は、非常に人気があり、成功を収めている Java アプリケーション開発フレームワークです。Spring Security は、Spring フレームワークに基づいて、Web アプリケーションのセキュリティのための完全なソリューションを提供します。一般に、Web アプリケーションのセキュリティには、ユーザー認証 (Authentication*) とユーザー認可 (Authorization) の2 つの部分が含まれます。

(1) ユーザ認証とは、ユーザがシステム内の正当な主体であるかどうか、つまりシステムにアクセスできるかどうかを確認することを指します。ユーザー認証では通常、ユーザーはユーザー名とパスワードを入力する必要があります。システムは、ユーザー名とパスワードを確認することで認証プロセスを完了します。

(2) ユーザー権限とは、ユーザーが操作を実行する権限を持っているかどうかを確認することを指します。システム内では、ユーザーごとに異なる権限が与えられます。たとえば、ファイルの場合、一部のユーザーは読み取りのみができますが、他のユーザーは変更できます。一般に、システムはさまざまなユーザーにさまざまな役割を割り当て、各役割は一連の権限に対応します。

Spring Security は実際にフィルターを使用して、複数のリクエストのパスをフィルターします。

(1) Session ベースの場合、Spring-security は cookie 内の sessionid を分析し、サーバーに保存されているセッション情報を見つけて、現在のユーザーがリクエストの要件を満たしているかどうかを判断します。

(2) トークンの場合はトークンを解析し、S​​pring-securityが管理する権限情報に現在のリクエストを追加する

2. 認証と認可の実装アイデア

システム内に多数のモジュールがある場合、各モジュールを認可および認証する必要があるため、トークンベースの認可および認証を選択します。ユーザーはユーザー名とパスワードに従って認証に成功し、一連の許可値を取得します。現在のユーザー ロールを使用し、ユーザーの名前がキーであり、アクセス許可リストが値の形式で Redis キャッシュに保存され、ユーザー名関連情報に従ってトークンが生成されて返されます。ブラウザーはトークンを次の場所に記録します。 API インターフェイスが呼び出されるたびに、デフォルトでトークンがヘッダー要求ヘッダーに渡されます。Spring-security はヘッダーを解析してトークン情報を取得し、トークンを解析して現在のユーザー名を取得し、アクセス許可リストを取得します。ユーザー名に応じて redis を作成し、Spring セキュリティが現在のリクエストにアクセス許可があるかどうかを判断できるようにします。

2. Spring Securityの統合

1. まず Spring Security 関連のモジュールとコード アーキテクチャを作成します

画像.png

画像.png

2. spring_security に関連する依存関係を導入する

<dependencies>
    <dependency>
        <groupId>com.jiangxia</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- Spring Security依赖 -->

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>
复制代码

3. spring_security を使用する必要があるモジュールに spring_security 依存関係を導入する

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>spring_security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
复制代码

4. コアコードの説明

Spring Security のコア構成は、WebSecurityConfigurerAdapter の構成を継承し、@EnableWebSecurity アノテーションを付けることです。この設定では、ユーザー名とパスワードの処理方法、リクエスト パスのオープンとクローズ、ログインとログアウトの制御、およびその他のセキュリティ関連の設定を指定します。メインのコードは次のとおりです。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private TokenManager tokenManager;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private RedisTemplate redisTemplate;
    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }
    /**
     * 密码处理
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }
    //配置哪些请求不拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
               );
    }
}
复制代码

認証および認可関連のツール クラスを作成します。

1. パスワードの処理方法

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    public DefaultPasswordEncoder() {
        this(-1);
    }
    /**
     * @param strength
     *            the log rounds to use, between 4 and 31
     */
    public DefaultPasswordEncoder(int strength) {
    }
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}
复制代码

2. トークン操作用のツールクラス

@Component
public class TokenManager {
    private long tokenExpiration = 24*60*60*1000;
    private String tokenSignKey = "123456";
    public String createToken(String username) {
        String token = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    public String getUserFromToken(String token) {
        String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return user;
    }
    public void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }
}
复制代码

3. ログアウトおよびログイン実装クラス

/**
 * 
 * 登出业务逻辑类
 *
 */
public class TokenLogoutHandler implements LogoutHandler {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader("token");
        if (token != null) {
            tokenManager.removeToken(token);
            //清空当前用户缓存中的权限数据
            String userName = tokenManager.getUserFromToken(token);
            redisTemplate.delete(userName);
        }
        ResponseUtil.out(response, R.ok());
    }
}
复制代码

4. 不正行為に対する統一処理方法

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码

5. 認証および認可フィルターを作成する

/**
 * 
 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验,认证
 * 
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                            Authentication auth) throws IOException, ServletException {
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
        ResponseUtil.out(res, R.ok().data("token", token));
    }
    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码
/**
 * 访问过滤器:授权filter
 * 
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);
            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }
            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}
复制代码

要約する

上記は、springboot の Spring Security の関連ロジックに関するものです。Spring セキュリティの承認と認証のプロセスは、パーミッション インターセプトのカスタム インターセプターのプロセスとほぼ同じです。認証プロセスでは、クライアント ユーザーがログインし、サーバーがユーザーのログイン情報をキャッシュし、最後にサーバーがユーザー情報 (基本情報、権限、トークンなど) をクライアントに返します。認可のプロセスでは、まずクライアントがトークンを使用してリクエストを開始し、サーバーがトークンを解析してユーザーがログインしているかどうかを判断し、次にキャッシュからユーザーのメニューをクエリし、ユーザーがリクエストする権限があるかどうかを判断します。メニューを選択し、最後にデータをクライアントに返します。

関連する提案

おすすめ

転載: blog.csdn.net/qq_41153943/article/details/125118857