springboot3專案的搭建四.1(security登入認證配置)

与f發表於2024-05-31

SpringBoot3 整合SpringSecurity

Maven

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

JwtFilter

package com.system.security;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.system.common.exception.ServerException;
import com.system.common.utlis.jwt.JwtUtils;
import com.system.common.utlis.result.Prefix;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * @author kuaiting
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Resource
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 實現過濾邏輯
        String authorization = request.getHeader(Prefix.TOKEN_KEY);
        System.out.println(authorization);
        if (authorization != null && authorization.startsWith(Prefix.TOKEN_BEARER)) {
            // 驗證JWT並設定使用者資訊到SecurityContextHolder中
            DecodedJWT jwt = jwtUtils.resolveToken(authorization);
            if (jwt != null) {
                Long uid = jwtUtils.getUid(jwt);
                SecurityUser details = jwtUtils.getUserDetails(jwt);
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
                request.setAttribute("uid", uid);
            }
        }
        filterChain.doFilter(request, response);
    }


    public void validate(HttpServletRequest request) throws ServerException {
        //請求中傳來的驗證碼
        String code = request.getParameter("code");
        String sessionCode = request.getSession().getAttribute("session_code").toString();
        if (StrUtil.isEmpty(code)) {
            throw new ServerException("驗證碼不能為空!");
        }
        if (StrUtil.isEmpty(sessionCode)) {
            throw new ServerException("驗證碼已經失效!");
        }
        if (!sessionCode.equalsIgnoreCase(code)) {
            throw new ServerException("驗證碼輸入錯誤!");
        }

    }

}

SecurityConfig

package com.system.security;

import com.system.common.utlis.jwt.JwtUtils;
import com.system.common.utlis.result.Prefix;
import com.system.common.utlis.result.ResData;
import com.system.common.utlis.result.ResEnum;
import com.system.system.dao.RoleDao;
import com.system.system.entity.vo.AuthVO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author kuaiting
 */
@Configuration
public class SecurityConfig {

    @Resource
    JwtUtils jwtUtils;

    @Resource
    private JwtFilter jwtFilter;

    @Resource
    private RoleDao roleDao;



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

    private final String[] paths = {
            "/druid/**", "/system/captcha/line",
            "/druid/login.html/**",
            "/system/login", "/js/**", "/*/*.json", "/*/*.yml",
            "/prims/**", "/type/**", "/system/file/**",
            "/diagram-viewer/**", "/images/**",
            "/api/login/**", "/api/file/**",
            "/css/**", "/*/*.ico", "/swagger-resources/**",
            "/swagger/**", "/swagger-ui/**",
            "/webjars/**", "/v3/**", "/v2/**", "/doc.html/**"
    };

    @Bean
    public SecurityFilterChain securityChain(HttpSecurity http) throws Exception {

        return http.authorizeHttpRequests(conf -> conf.requestMatchers(paths).permitAll()
                .anyRequest().authenticated())
                .formLogin(conf ->
                        conf.loginProcessingUrl("/system/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .successHandler(this::onAuthenticationSuccess)
                        .failureHandler(this::onAuthenticationFailure))
                .exceptionHandling(conf ->
                         conf.authenticationEntryPoint(this::noLogin)
                        .accessDeniedHandler(this::noPermission))
                .logout(conf ->
                         conf.logoutUrl("/system/logout")
                        .logoutSuccessHandler(this::onLogoutSuccess))
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .build();

    }

    private void noPermission(HttpServletRequest request,
                              HttpServletResponse response,
                              AccessDeniedException e) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.FORBIDDEN.getCode(), ResEnum.FORBIDDEN.getMsg());

    }

    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        SecurityUser user = (SecurityUser) authentication.getPrincipal();
        Long uid = user.getId();
        Set<String> permissions = new HashSet<>();
        for (GrantedAuthority authority : user.getAuthorities()) {
            String auth = authority.getAuthority();
            permissions.add(auth);
        }
        String token = jwtUtils.createToken(user, uid, user.getUsername(),user.getPassword());
        AuthVO authVo = new AuthVO();
        authVo.setRole(roleDao.getUserRoles(uid));
        authVo.setPermission(permissions);
        authVo.setKey(Prefix.TOKEN_KEY);
        authVo.setToken(token);
        authVo.setExpire(jwtUtils.expireTime().getTime());
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg(), authVo);
    }

    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), exception.getMessage());
    }

    public void noLogin(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        jsonWriter(writer, ResEnum.UNAUTHORIZED.getCode(), ResEnum.UNAUTHORIZED.getMsg());
    }


    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication) throws IOException {
        String authorization = request.getHeader("Authorization");
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = response.getWriter();
        // 登出時,刪除redis中的token
        if (jwtUtils.invalidateToken(authorization)) {
            jsonWriter(writer, ResEnum.SUCCESS.getCode(), ResEnum.SUCCESS.getMsg());
        } else {
            jsonWriter(writer, ResEnum.FAIL.getCode(), ResEnum.FAIL.getMsg());
        }


    }

    private void jsonWriter(PrintWriter writer, Integer code, String message) {
        jsonWriter(writer, code, message, null);
    }

    private void jsonWriter(PrintWriter writer, Integer code, String message, Object data) {
        writer.write(ResData.asJson(code, message, data));
        writer.flush();
        writer.close();
    }


    private List<Long> getRoleIds(Long uid) {
        return roleDao.getRoleIds(uid);
    }

    private List<String> getUserRoles(Long uid) {
        return roleDao.getUserRoles(uid);
    }

    private Set<String> getPermissions(List<Long> roleIds) {
        Set<String> all = new HashSet<>();
        for (Long id : roleIds) {
            List<String> permissions = getUserPermissions(id);
            all.addAll(permissions);
        }
        return all;
    }

    private List<String> getUserPermissions(Long rid) {
        return roleDao.getPermissions(rid);
    }


}

User

package com.system.security;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author kuaiting
 */
@Data
@NoArgsConstructor
public class SecurityUser implements UserDetails, Serializable {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private Set<String> permissions;
    private  Collection<? extends GrantedAuthority> authorities;


    public SecurityUser( Long id,String username, String password, Integer status, Set<String> permissions) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.status = status;
        this.permissions = permissions;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null) {
            return authorities;
        }
        //把permissions中字串型別的許可權資訊轉換成GrantedAuthority物件存入authorities中
        authorities = permissions.stream().
                map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return status == 0;
    }
}

JwtUtils

package com.system.common.utlis.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.system.common.utlis.redis.RedisService;
import com.system.common.utlis.result.Prefix;
import com.system.security.SecurityUser;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
public class JwtUtils {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expire}")
    private Integer expire;

    @Resource
    private RedisService redisService;

    public Boolean invalidateToken(String haeadToken) {
        String token = convertToken(haeadToken);
        if (token == null) {
            return false;
        }
        try {
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
            return deleteToken(jwt.getId(), jwt.getExpiresAt());
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    // 刪除redis 中的Token
    private Boolean deleteToken(String id, Date date) {
        if (isInvalidToken(id)) {
            return false;
        }
        Date now = new Date();
        long expire = Math.max(date.getTime() - now.getTime(), 0);
        redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire);
        return true;
    }

    // 驗證 redis 中 token 是否存在
    private Boolean isInvalidToken(String id) {
        return Boolean.TRUE.equals(redisService.hasKey(Prefix.JWT_BLACK_LIST + id));
    }


    public String createToken(UserDetails details, Long id, String username,String password) {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        redisService.set(Prefix.JWT_BLACK_LIST + id, id, expire);
        return JWT.create().withJWTId(String.valueOf(id))
                .withClaim("id", id)
                .withClaim("username", username)
                .withClaim("password", password)
                .withClaim("authorities", getAuths(details))
                .withExpiresAt(expireTime())
                .sign(algorithm);
    }

    public String getAuths(UserDetails details) {
        return details.getAuthorities().stream().map(GrantedAuthority::getAuthority).
                collect(Collectors.joining(","));
    }

    // 獲取過期時間
    public Date expireTime() {
        // 過期時間
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.HOUR, expire * 24); // 預設7天
        return instance.getTime();
    }

    // 解析 JWT token
    public DecodedJWT resolveToken(String haeadToken) {
        String token = convertToken(haeadToken);
        if (token == null) {
            return null;
        }
        try {
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
            if (isInvalidToken(jwt.getId())) {
                return null;
            }
            Date expires = jwt.getExpiresAt();
            return new Date().after(expires) ? null : jwt;
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    // 解析 擷取真正有用的token
    private String convertToken(String haeadToken) {
        if (haeadToken == null || !haeadToken.startsWith(Prefix.TOKEN_BEARER)) {
            return null;
        }
        return haeadToken.substring(7);
    }

    // 獲取使用者資訊
    public SecurityUser getUserDetails(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        String authorities = claims.get("authorities").asString();
        Set<SimpleGrantedAuthority> permissions = new HashSet<>();
        for (String auth : authorities.split(",")) {
            permissions.add(new SimpleGrantedAuthority(auth));
        }
        SecurityUser sysUser = new SecurityUser();
        sysUser.setId(jwt.getClaim("id").asLong());
        sysUser.setUsername(claims.get("username").toString());
        sysUser.setPassword(claims.get("password").toString());
        sysUser.setStatus(0);
        sysUser.setAuthorities(permissions);
        return sysUser;
    }

    // 獲取使用者ID
    public Long getUid(DecodedJWT jwt) {
        return jwt.getClaim("id").asLong();
    }
}

轉:https://blog.csdn.net/qq_45834006/article/details/136403939

https://docs.spring.io/spring-security/reference/index.html

相關文章