前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringSecurity学习

SpringSecurity学习

作者头像
云边小卖部
发布2022-12-02 10:06:17
6450
发布2022-12-02 10:06:17
举报
文章被收录于专栏:技术分享1技术分享1

SpringSecurity学习

介绍

spring security 的核心功能主要包括:

  • 用户认证(是否有登录用户)
  • 授权(授权用户权限,能做些什么)
  • 攻击防护(防止身份伪造,防范 CSRF 攻击 )

其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式

整合SpringBoot

加入依赖

代码语言:javascript
复制
<dependencies>
	<!-- ... other dependency elements ... -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
</dependencies>

没有 Spring Boot 的 Maven

代码语言:javascript
复制
<dependencyManagement>
	<dependencies>
		<!-- ... other dependency elements ... -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-bom</artifactId>
			<version>{spring-security-version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

以下是 Spring Security Filter 排序的完整列表:

  • ForceEagerSessionCreationFilter
  • ChannelProcessingFilter
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

Spring Security 在 Servlet 身份验证中使用的主要架构组件

SecurityContextHolder上下文持有者

SecurityContextHolder是 Spring Security 存储 身份验证 。 Spring Security 并不关心如何 SecurityContextHolder被填充。 如果它包含一个值,则将其用作当前经过身份验证的用户。

示例:

代码语言:javascript
复制
SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 
java
  • 我们首先创建一个空的 SecurityContext. 你应该创建一个新的 SecurityContext实例而不是使用 SecurityContextHolder.getContext().setAuthentication(authentication)避免跨多个线程的竞争条件。
  • 接下来,我们新建一个 Authentication目的。 Spring Security 不在乎什么类型 Authentication实施设置在 SecurityContext. 在这里,我们使用 TestingAuthenticationToken,因为它非常简单。
  • 更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities).最后,我们设置 SecurityContext在 SecurityContextHolder. Spring Security 使用此信息进行授权

要获取有关经过身份验证的主体的信息,请访问上下文持有者

访问当前经过身份验证的用户

代码语言:javascript
复制
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
//用户name
String username = authentication.getName();
//用户基本信息
Object principal = authentication.getPrincipal();
//获取用户角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
java

Authentication 验证

Authentication接口在 Spring Security 中有两个主要用途:

  • 一个输入 AuthenticationManager提供用户提供的身份验证凭据。 在这种情况下使用时, isAuthenticated()返回 false.
  • 表示当前经过身份验证的用户。 您可以获得当前 AuthenticationSecurityContext

Authentication包含:

  • principal: 标识用户。 使用用户名/密码进行身份验证时,这通常是 UserDetails.
  • credentials: 通常是密码。 在很多情况下,这在用户通过身份验证后会被清除,以确保它不被泄露。
  • authorities: 这 GrantedAuthority实例是授予用户的高级权限。 两个例子是角色和范围。

具体实现

现阶段理解主要流程,首先配置SpringSecurity的配置类。

SecurityConfig.java

代码语言:javascript
复制
package com.example.springstudy.config;

import com.example.springstudy.filter.JWTAuthenticationTokenFilter;
import com.example.springstudy.handler.AccessDeniedHandlerImpl;
import com.example.springstudy.handler.AuthenticationPointImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Author sunjl
 * @Date 2022/7/5 17:24
 * @Version 1.0.0
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JWTAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Autowired
    private AuthenticationPointImpl authenticationPoint;
    @Autowired
    private AccessDeniedHandlerImpl accessDeniedHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置放行
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable()
//                //不通过session获取SecurityContext
//                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//                .and()
//                .authorizeRequests()
//                //登录接口允许匿名访问
//                .antMatchers("/user/login").anonymous()
////                拦截其他接口
//                .anyRequest()
//                .authenticated();
//将自定义的拦截器放在Sercurity拦截器中的UsernamePasswordAuthenticationFilter之前
        http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                //没登录也可以访问
                .antMatchers("/user/login").anonymous()
                //其他请求认证之后可以访问
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//        配置异常处理器
        http.exceptionHandling().authenticationEntryPoint(authenticationPoint).accessDeniedHandler(accessDeniedHandler);
        http.cors();
    }
}
java

当用户请求登录,发送用户名和密码到服务器,通过过滤器,到UserDetailService的实现类中,通过用户名判断是否存在当前用户,存在当前用户同时将当前用户的角色和信息封装到一个UserDtailsService类中并返回。

UserDetailServiceImpl.java

代码语言:javascript
复制
package com.example.springstudy.service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.springstudy.entity.LoginUser;
import com.example.springstudy.entity.User;
import com.example.springstudy.mapper.MenuMapper;
import com.example.springstudy.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @Author sunjl
 * @Date 2022/7/5 14:42
 * @Version 1.0.0
 */
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    MenuMapper menuMapper;

    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        //new QueryWrapper 不能直接使用Lambad表达式
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        log.info(name+"-=======");
        queryWrapper.eq(User::getUserName,name);
        User user = userMapper.selectOne(queryWrapper);
        if (Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        //查询用户权限
        List<String> userRole = menuMapper.selectUserRole(user.getId());
//        List<String> userRole=new ArrayList<>(Arrays.asList("test"));
        return new LoginUser(user,userRole);
    }
}
java

整合JWT

JWT工具类

JwtUtil.java

代码语言:javascript
复制
package com.example.springstudy.utills;

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;

/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    
    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成jtw
     * @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("sg")     // 签发者
                .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 jwt = createJWT("2123");
        Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");
        String subject = claims.getSubject();
        System.out.println(subject);
//        System.out.println(claims);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.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();
    }


}
java

配置SpringSecurity拦截器

代码语言:javascript
复制
package com.example.springstudy.filter;

import com.alibaba.fastjson.JSON;
import com.example.springstudy.entity.LoginUser;
import com.example.springstudy.utills.JwtUtil;
import com.example.springstudy.utills.RedisCache;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * @Author sunjl
 * @Date 2022/7/6 9:40
 * @Version 1.0.0
 */
@Slf4j
@Component
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = httpServletRequest.getHeader("token");
        if (!StringUtils.hasText(token)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
            log.info("userId====" + userId);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //获取用户信息
        LoginUser loginUser =  redisCache.getCacheObject("userInfo_"+userId);
        log.info("权限信息:"+loginUser.getAuthorities());
//        LoginUser loginUser = JSON.parseObject(JSON.toJSONString(o), LoginUser.class);
        if (Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //TODO 获取权限信息封装到Authentication中
        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
     //将用户信息存放在SecurityContext中
//        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        securityContext.setAuthentication(authenticationToken);
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}
java

配置的拦截器需要在配置类中配置拦截器的信息。

其他SpringSecurity中自带的用户的未登录(AuthenticationEntryPoint)、权限不足(AccessDeniedHandler)、登录失败(AuthenticationFailureHandler)、登录成功(AuthenticationSuccessHandler)、退出登录(LogoutSuccessHandler)的配置

例子:

没有权限:

代码语言:javascript
复制
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new Result(false, StatusConst.AUTHORIZED,"没有操作权限")));
    }

}
java

未登录

代码语言:javascript
复制
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new Result(false, StatusConst.NOT_LOGIN, "请登录")));
    }

}
java

登录失败

代码语言:javascript
复制
@Component
public class AuthenticationFailHandlerImpl implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new Result(false, StatusConst.ERROR,e.getMessage())));
    }

}
java

登录成功

代码语言:javascript
复制
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
    @Autowired
    private UserAuthDao userAuthDao;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
        // 更新用户ip,最近登录时间
        updateUserInfo();
        UserLoginDTO userLoginDTO = BeanCopyUtil.copyObject(UserUtil.getLoginUser(), UserLoginDTO.class);
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new Result<UserInfoDTO>(true, StatusConst.OK, "登录成功!", userLoginDTO)));
    }
}
java

注销

代码语言:javascript
复制
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new Result(true, StatusConst.OK,"注销成功")));
    }

}
java
代码语言:javascript
复制
run.getBean(DefaultSecurityFilterChain.class)
java
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-07-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringSecurity学习
    • 介绍
      • 整合SpringBoot
        • 没有 Spring Boot 的 Maven
      • Spring Security 在 Servlet 身份验证中使用的主要架构组件
        • SecurityContextHolder上下文持有者
          • 要获取有关经过身份验证的主体的信息,请访问上下文持有者
            • Authentication 验证
              • 具体实现
                • 整合JWT
                  • 没有权限:
                    • 未登录
                      • 登录失败
                        • 登录成功
                          • 注销
                          相关产品与服务
                          多因子身份认证
                          多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档