springboot3.2.8【security登录认证】

概要

springboot3.2.8,使用security登录认证

整体架构流程

代码思路

1.配置WebSecurityConfig类,配置正给http请求的认证和鉴权
2.自定义拦截器customizeAuthenticationFilter(),+账号密码验证UsernamePasswordAuthenticationFilter对用户认证

代码展示

  • WebSecurityConfig 类,这里隐藏了一些内部使用的包。大家用可替换的包和方法函数解决。
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 该类定义了SpringSecurity的主要流程配置
 */

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
//从配置文件中的前缀获取配置
@ConfigurationProperties(prefix = "system")
@Data
public class WebSecurityConfig {

    //handler定义了很多 通知
    @Autowired
    private CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;

    @Autowired
    private CustomizeAuthenticationFailureHandler customizeAuthenticationFailureHandler;

    @Autowired
    private CustomizeLogoutSuccessHandler customizeLogoutSuccessHandler;

    @Autowired
    //鉴权token过滤器
    private JwtAuthenticationToKenFilter jwtAuthenticationToKenFilter;

    @Autowired
    private CustomizeAccessDeniedHandler customizeAccessDeniedHandler;

    @Autowired
    private CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenService tokenService;

    //定义了白名单
    private String[] whiteList;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws  Exception{

        httpSecurity
                //禁用csrf 因为不使用session
                .csrf(csrf->csrf.disable())
                //禁用自带的跨域处理
                .cors(cors->cors.disable())
                //基于token 关掉session
                .sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //权限路径控制
                .authorizeHttpRequests(request->{
                    request
                            .requestMatchers(whiteList)
                            .permitAll()
                            /*.requestMatchers(HttpMethod.GET,"/","/*.html","/**.html","/**.css","/**.js","/profile/**","/grpc/**")
                            .permitAll()
                            .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/webjars/**", "/v3/**")
                            .permitAll()
                            .requestMatchers("/auth/**")
                            .permitAll()*/
                            .anyRequest().authenticated();
                })
                //登录,这里将登录注释。依然可以通过POST带参数访问/login
                /*.formLogin(login->{
                    login
                            //登录接口地址
                            .loginProcessingUrl("/login")
                            //允许所有用户登录
                            .permitAll()
                            //登录成功处理
                            .successHandler(customizeAuthenticationSuccessHandler)
                            //登录失败处理
                            .failureHandler(customizeAuthenticationFailureHandler);

                })*/
                //注销
                .logout(logout->{
                    logout
                            .logoutUrl("/logout")
                            //注销成功处理
                            .logoutSuccessHandler(customizeLogoutSuccessHandler);
                })
                //配置自定义filter
                .addFilterAt(customizeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                //jwt权限过滤
//                .addFilterBefore(jwtAuthenticationToKenFilter, CustomizeAuthenticationFilter.class)
                //异常处理
                .exceptionHandling(exception->{
                    exception
                            //认证失败处理
                            .authenticationEntryPoint(customizeAuthenticationEntryPoint)
                            //权限失败处理
                            .accessDeniedHandler(customizeAccessDeniedHandler);
                });


        return httpSecurity.build();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义filter配置
     * 对用户信息验证,并设置成功和失败Handler
     * @return
     */
    @Bean
    public CustomizeAuthenticationFilter customizeAuthenticationFilter(){
        CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
        //必须设置authenticationManager
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(customizeAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(customizeAuthenticationFailureHandler);
        return filter;
    }

    /**
     * 注入userDetailsService和加解密Encoder,userDetailsService回去调用我们的实现类
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManager(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(bCryptPasswordEncoder());
        ProviderManager pm = new ProviderManager(provider);
        return pm;
    }

}

  • customizeAuthenticationFilter自定义拦截器,验证用户信息
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;
import java.util.Map;

/**
 * 自定义filter
 * 处理json传参
 * 处理password解码
 */
public class CustomizeAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            if(request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
                Map<String,String> map;
                UsernamePasswordAuthenticationToken authRequest = null;
                try{
                    //转换输入的json为map格式
                    map = new ObjectMapper().readValue(request.getInputStream(),Map.class);
                    String username = map.get(getUsernameParameter());
                    username = username != null ? username.trim() : "";
                    String password = map.get(getPasswordParameter());
                    password = password != null ? password : "";
                    //解密
                    String decryptedPassword = RSAEncrypt.decode(password);
                    authRequest = new UsernamePasswordAuthenticationToken(username,decryptedPassword);

                }catch (IOException e){
                    e.printStackTrace();
                    authRequest = new UsernamePasswordAuthenticationToken("","");
                }finally {
                    setDetails(request,authRequest);
                    //调用authenticate完成认证
                    return this.getAuthenticationManager().authenticate(authRequest);
                }
            }
        }
        //不是json格式按照表单登录逻辑处理
        return super.attemptAuthentication(request, response);
    }

}

  • 自定义实现类UserDetailsServiceImpl,用于查询数据库验证用户
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

/**
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Autowired
    private IRoleService roleService;

    @Autowired
    private IPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserName,username);
        wrapper.eq(User::getDelFlag, UserStatus.OK);
        User user = userService.getOne(wrapper);
        if(StringUtils.isNull(user)){
            //throw new UsernameNotFoundException("账号不存在");
            throw new ServiceException("账号不存在");
        }
        UsernameContextHolder.setContext(username);

        //查询角色
        List<Role> roles = roleService.queryRolesByUser(user.getUserId());

        //查询权限
        Set<String> permissions = permissionService.queryPermissionByRole(roles);

        return new LoginUser().setUserId(user.getUserId()).setUser(user).setRoles(roles).setPermissions(permissions);
    }
}

  • LoginUser,自定义的实例
import cn.hutool.core.collection.CollectionUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.*;

/**
需要实现UserDetails 
 */
@Data
@Accessors(chain = true)
public class LoginUser implements UserDetails {

    private Long userId;

    private String token;

//    user为普通的用户实体类
    private User user;

    private List<Role> roles;

    private Set<String> permissions;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(roles)){
            roles.forEach(role->{
                MyGrantedAuthority authority = new MyGrantedAuthority(role);
                //redis对SimpleGrantedAuthority反序列化有问题
                //GrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleKey());
                authorities.add(authority);
            });

        }
        return authorities;
    }


    public static class MyGrantedAuthority implements GrantedAuthority {
        private Role role;

        private String roleKey;

        @Override
        public String getAuthority() {
            return Objects.isNull(this.role) ? this.roleKey : "ROLE_"+this.role.getRoleKey();
        }

        public MyGrantedAuthority(Role role) {
            this.role = role;

        }

        public MyGrantedAuthority(String roleKey) {
            this.roleKey = roleKey;
        }
    }

    @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 user.getLockFlag() == FlagStatus.NOT.getCode();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

  • jwt token验证
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 */
@Component
public class JwtAuthenticationToKenFilter extends OncePerRequestFilter {


    @Autowired
    private TokenService tokenService;

    @Autowired
    private RedisService redisService;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = tokenService.getRequestToken(request);

        //验证token是否有效
        LoginUser loginUser = tokenService.getUserByToken(token);

        if(StringUtils.isNotNull(loginUser)){
            //判断token是否需要续期
            Long expireTime = redisService.getExpire(CacheConstants.LOGIN_USER_TOKEN_KEY + loginUser.getUserId());
            if(expireTime>0 && expireTime<=600L){
                tokenService.refreshToken(loginUser);
            }
        }

        if(StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);

        }
        filterChain.doFilter(request,response);
    }
}

  • token的管理
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Component
@ConfigurationProperties(prefix = "jwt")
@Data
@Slf4j
public class TokenService {

    // 令牌自定义标识
    private String header;

    // 令牌秘钥
    private String secret;

    // 令牌过期时间
    private Long expireTime;

    @Autowired
    private RedisService redisService;

    /**
     * 创建令牌
     *
     * @param loginUser
     * @return
     */
    public String createToken(LoginUser loginUser) {

        String token = UUID.randomUUID().toString();
        loginUser.setToken(token);
        refreshToken(loginUser);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);

        return createToken(claims);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims
     * @return
     */
    private String createToken(Map<String, Object> claims) {
        /*return Jwts.builder()
                .setClaims(claims)
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("jacob-user")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();*/
        return Jwts.builder()
                .claims(claims)
                .subject("jacob-user")
                .issuedAt(new Date())
                .signWith(Keys.hmacShaKeyFor(secret.getBytes()), Jwts.SIG.HS256)
                .compact();
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        //缓存(token,user)
        redisService.setCacheObject(getTokenKey(loginUser.getToken()), loginUser, expireTime, TimeUnit.MINUTES);

        //缓存(user,token)
        redisService.setCacheObject(getUserTokenKey(loginUser), loginUser.getToken(), expireTime, TimeUnit.MINUTES);

    }

    /**
     * 删除用户身份信息
     */
    public void deleteToken(LoginUser loginUser) {
        if (StringUtils.isNotEmpty(loginUser.getToken())) {
            redisService.deleteObject(getTokenKey(loginUser.getToken()));
            redisService.deleteObject(getUserTokenKey(loginUser));
        }
    }

    /**
     * 删除用户身份信息
     */
    public void deleteUserToken(LoginUser loginUser) {
        if(redisService.hasKey(getUserTokenKey(loginUser))){
            String token = redisService.getCacheObject(getUserTokenKey(loginUser));
            redisService.deleteObject(getTokenKey(token));
            redisService.deleteObject(getUserTokenKey(loginUser));
        }
    }

    private String getTokenKey(String uuid) {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }

    private String getUserTokenKey(LoginUser loginUser) {
        return CacheConstants.LOGIN_USER_TOKEN_KEY + loginUser.getUserId();
    }


    /**
     * 获取请求中的token
     *
     * @param request
     * @return
     */
    public String getRequestToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }


    /**
     * 获取用户信息
     *
     * @param token
     * @return
     */
    public LoginUser getUserByToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        try {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = claims.get(Constants.LOGIN_USER_KEY, String.class);
            return redisService.getCacheObject(getTokenKey(uuid));
        } catch (Exception e) {
            log.error("验证token失败!{}", e.getMessage());
        }
        return null;
    }


    /**
     * 从令牌中获取数据声明
     *
     * @param token
     * @return
     */
    private Claims parseToken(String token) {
        /*return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();*/

        return Jwts.parser()
                .verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }


    /**
     *
     * 清除锁定缓存
     * @param username
     */
    public void clearLoginRecordCache(String username){
        if (redisService.hasKey(getPasswordErrorKey(username))) {
            redisService.deleteObject(getPasswordErrorKey(username));
        }
    }

    private String getPasswordErrorKey(String username){
        return CacheConstants.PWD_ERR_CNT_KEY + username;
    }

}

  • 成功通知类CustomizeAuthenticationSuccessHandler
import cn.hutool.extra.servlet.JakartaServletUtil;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 登录成功处理
 */
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private IUserService userService;

    @Autowired
    private SystemConfig systemConfig;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //登录日志
        Thread.ofVirtual().start(()->{
            AsyncFactory.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"),
                    UserAgent.parseUserAgentString(request.getHeader("User-Agent")), JakartaServletUtil.getClientIP(request));
        });

        //更新登录信息
        User user = loginUser.getUser();
        user.setLoginTime(System.currentTimeMillis());
        userService.updateById(user);

        //单点登录
        if (systemConfig.getOnlyOneLogin()) {
            tokenService.deleteUserToken(loginUser);
        }

        //删除锁定缓存
        tokenService.clearLoginRecordCache(user.getUserName());

        //生成token
        String token = tokenService.createToken(loginUser);
        ResponseUtil.responseToken(response,token);

    }


}

小结

整体内容比较简单,主要用于给新手理一下思路,更快的学习使用。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值