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

与f發表於2024-05-31

SpringBoot3+SpringSecurity整合

Security導包:

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

模擬Redis儲存登入資訊:

public class CacheEntity implements Serializable {
    private Object value;
    /**
     * 儲存的時間戳
     */
    private long gmtModify;
    /**
     * 過期時間
     */
    private int expire;
 
    public Object getValue() {
        return value;
    }
 
    public void setValue(Object value) {
        this.value = value;
    }
 
    public long getGmtModify() {
        return gmtModify;
    }
 
    public void setGmtModify(long gmtModify) {
        this.gmtModify = gmtModify;
    }
 
    public int getExpire() {
        return expire;
    }
 
    public void setExpire(int expire) {
        this.expire = expire;
    }
 
    public CacheEntity(Object value, long gmtModify, int expire) {
        this.value = value;
        this.gmtModify = gmtModify;
        this.expire = expire;
    }
}

@Slf4j
public class LocalCache {
    /**
     * 預設的快取容量
     */
    private static final int DEFAULT_CAPACITY = 512;
    /**
     * 最大容量
     */
    private static final int MAX_CAPACITY = 100000;
    /**
     * 重新整理快取的頻率
     */
    private static final int MONITOR_DURATION = 2;
 
    // 啟動監控執行緒
    static {
        new Thread(new TimeoutTimerThread()).start();
    }
 
    // 內部類方式實現單例
    private static class LocalCacheInstance {
        private static final LocalCache INSTANCE = new LocalCache();
    }
 
    public static LocalCache getInstance() {
        return LocalCacheInstance.INSTANCE;
    }
 
    private LocalCache() {
    }
 
    /**
     * 使用預設容量建立一個Map
     */
    private static Map<String, CacheEntity> cache = new ConcurrentHashMap<>(DEFAULT_CAPACITY);
 
    /**
     * 將key-value儲存到本地快取並制定該快取的過期時間
     *
     * @param key
     * @param value
     * @param expireTime 過期時間,如果是-1 則表示永不過期
     * @param <T>
     * @return
     */
    public <T> boolean putValue(String key, T value, int expireTime) {
        return putCloneValue(key, value, expireTime);
    }
 
    /**
     * 將值透過序列化clone 處理後儲存到快取中,可以解決值引用的問題
     *
     * @param key
     * @param value
     * @param expireTime
     * @param <T>
     * @return
     */
    private <T> boolean putCloneValue(String key, T value, int expireTime) {
        try {
            if (cache.size() >= MAX_CAPACITY) {
                return false;
            }
            // 序列化賦值
            CacheEntity entityClone = clone(new CacheEntity(value, System.nanoTime(), expireTime));
            cache.put(key, entityClone);
            return true;
        } catch (Exception e) {
            log.error("新增快取失敗:{}", e.getMessage());
        }
        return false;
    }
 
    /**
     * 序列化 克隆處理
     *
     * @param object
     * @param <E>
     * @return
     */
    private <E extends Serializable> E clone(E object) {
        E cloneObject = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            oos.close();
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            cloneObject = (E) ois.readObject();
            ois.close();
        } catch (Exception e) {
            log.error("快取序列化失敗:{}", e.getMessage());
        }
        return cloneObject;
    }
 
    /**
     * 從本地快取中獲取key對應的值,如果該值不存則則返回null
     *
     * @param key
     * @return
     */
    public Object getValue(String key) {
        if (CollectionUtils.isEmpty(cache)) {
            return null;
        }
        CacheEntity cacheEntity = cache.get(key);
        if (ObjectUtils.isEmpty(cacheEntity)) {
            return null;
        }
        return cacheEntity.getValue();
    }
 
    public void remove(String key) {
        if (CollectionUtils.isEmpty(cache)) {
            return;
        }
        CacheEntity cacheEntity = cache.get(key);
        if (ObjectUtils.isEmpty(cacheEntity)) {
            return;
        }
        cache.remove(key);
    }
 
    /**
     * 清空所有
     */
    public void clear() {
        cache.clear();
    }
 
    /**
     * 過期處理執行緒
     */
    static class TimeoutTimerThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(MONITOR_DURATION);
                    checkTime();
                } catch (Exception e) {
                    log.error("過期快取清理失敗:{}", e.getMessage());
                }
            }
        }
 
        /**
         * 過期快取的具體處理方法  *  * @throws Exception
         */
        private void checkTime() throws Exception {
            // 開始處理過期
            for (String key : cache.keySet()) {
                CacheEntity tce = cache.get(key);
                long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - tce.getGmtModify());
                // 過期時間 : timoutTime
                if (tce.getExpire() > timoutTime) {
                    continue;
                }
                log.info(" 清除過期快取 :" + key);
                //清除過期快取和刪除對應的快取佇列
                cache.remove(key);
            }
        }
    }
}

許可權列舉:

//    許可權值是將二進位制與十進位制相互轉換來判斷的
public enum PermissionEnum {
 
    GET_DEPARTMENT(1, "單位獲取", "ROLE_GET_DEPARTMENT", 0x0000000000000001L),
    INSERT_DEPARTMENT(2, "單位增加", "ROLE_INSERT_DEPARTMENT", 0x0000000000000002L),
    UPDATE_DEPARTMENT(3, "單位修改", "ROLE_UPDATE_DEPARTMENT", 0x0000000000000004L),
    DELETE_DEPARTMENT(4, "單位刪除", "ROLE_DELETE_DEPARTMENT", 0x0000000000000008L),
    ;
 
    private int id;
 
    private String permissions;
 
    private String permissionNames;
 
    private Long value;
 
    PermissionEnum(int id, String permissions, String permissionNames, Long value) {
        this.id = id;
        this.permissions = permissions;
        this.permissionNames = permissionNames;
        this.value = value;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getPermissions() {
        return permissions;
    }
 
    public void setPermissions(String permissions) {
        this.permissions = permissions;
    }
 
    public String getPermissionNames() {
        return permissionNames;
    }
 
    public void setPermissionNames(String permissionNames) {
        this.permissionNames = permissionNames;
    }
 
    public Long getValue() {
        return value;
    }
 
    public void setValue(Long value) {
        this.value = value;
    }
 
    public static List<GrantedAuthority> fromCode(Long code) {
        List<GrantedAuthority> list = new ArrayList<>();
        PermissionEnum[] codes = PermissionEnum.values();
        for (PermissionEnum state : codes) {
            if ((state.getValue() & code) > 0) {
                list.add(new SimpleGrantedAuthority(state.getPermissionNames()));
            }
        }
        return list;
    }
 
    public static List<PermissionEnum> getAuthList(Long code) {
        List<PermissionEnum> list = new ArrayList<>();
        PermissionEnum[] codes = PermissionEnum.values();
        for (PermissionEnum state : codes) {
            if ((state.getValue() & code) > 0) {
                list.add(state);
            }
        }
        return list;
    }
 
    //  獲取許可權值
    public static Long getPermissionCode(Integer[] auths) {
        Long code = 0x0000000000000000L;
        PermissionEnum[] codes = PermissionEnum.values();
        for (Integer auth : auths) {
            for (PermissionEnum permissionCode : codes) {
                if (auth.equals(permissionCode.getId())) {
                    code += permissionCode.getValue();
                    break;
                }
            }
        }
        return code;
    }
 
    //    獲取許可權陣列
    public static String[] getAuths(Long code) {
        List<String> lists = new ArrayList<>();
        PermissionEnum[] codes = PermissionEnum.values();
        for (PermissionEnum state : codes) {
            if ((state.getValue() & code) > 0) {
                lists.add(state.getPermissions());
            }
        }
        return lists.toArray(new String[lists.size()]);
    }
 
    //    獲取許可權值
    public static Long getPermissionCode(String[] auths) {
        Long code = 0x0000000000000000L;
        PermissionEnum[] codes = PermissionEnum.values();
        for (String auth : auths) {
            for (PermissionEnum permissionCode : codes) {
                if (auth.equals(permissionCode.getPermissions())) {
                    code += permissionCode.getValue();
                    break;
                }
            }
        }
        return code;
    }
}

User實體類:

@Data
@Accessors(chain = true)
public class Users implements Serializable {
    private Long userID;
    private String userName;
    private String userPassword;
    private String userPhone;
    private String userAddress;
    private Integer userAllowErrCount;
    private Integer userErrCount;
    private Date userLastErrTime;
    private Long userRoleID;
    private Roles roles;
    private Long userDepID;
    private Department department;
    private boolean userEnable;
}

許可權反序列化:

public class CustomAuthorityDeserializer extends JsonDeserializer {
    @Override
    public Object deserialize(
            JsonParser p, DeserializationContext deserializationContext
    ) throws IOException, JacksonException {
        ObjectMapper mapper = (ObjectMapper) p.getCodec();
        JsonNode jsonNode = mapper.readTree(p);
        LinkedList<GrantedAuthority> grantedAuthorities = new LinkedList<>();
        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()) {
            JsonNode next = elements.next();
            JsonNode authority = next.get("authority");
            //將得到的值放入連結串列 最終返回該連結串列
            grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
        }
        return grantedAuthorities;
    }
}

使用者詳情類:

@JsonIgnoreProperties({"enabled", "accountNonExpired", "accountNonLocked", "credentialsNonExpired",
        "username", "password"})
public class MyUserDetail extends Users implements UserDetails, Serializable {
    List<? extends GrantedAuthority> authorities;
 
    public MyUserDetail() {
    }
 
    public MyUserDetail(Users users, List<? extends GrantedAuthority> authList) {
        this.setUserID(users.getUserID());
        this.setUserName(users.getUserName());
        this.setUserPassword(users.getUserPassword());
        this.setUserDepID(users.getUserDepID());
        this.setUserRoleID(users.getUserRoleID());
        this.authorities = authList;
    }
 
    @Override
    @JsonDeserialize(using = CustomAuthorityDeserializer.class)
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
 
    @Override
    public String getPassword() {
        return this.getUserPassword();
    }
 
    @Override
    public String getUsername() {
        return this.getUserName();
    }
 
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
 
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
 
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
}

使用者詳情實現類:

@Component
public class MyUserDetailServiceImpl implements UserDetailsService {
 
    //    運算元據庫,根據使用者名稱稱查詢使用者資訊
    private final UserMapper userMapper;
 
    public MyUserDetailServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users users = userMapper.getUserByName(username);
        Optional.ofNullable(users).orElseThrow(() -> {
            //    自定義的異常返回類和列舉
            throw new CommonException(YIXGResultEnum.USER_NOT_EXIST.getCode(),
                    YIXGResultEnum.USER_NOT_EXIST.getMessage());
        });
        if (ObjectUtils.isEmpty(users.getUserRoleID())) {
            throw new CommonException(YIXGResultEnum.USER_ROLE_NOT_EXIST.getCode(),
                    YIXGResultEnum.USER_ROLE_NOT_EXIST.getMessage());
        }
        List<GrantedAuthority> authorityList = PermissionEnum.fromCode(users.getRoles().getRolePermission());
        return new MyUserDetail(users, authorityList);
    }
}

攔截未登入請求:

/**
 * 使用者發起未登入的請求會被AuthorizationFilter攔截,並丟擲AccessDeniedException異常。異常被AuthenticationEntryPoint
 * 處理,預設會觸發重定向到登入頁。Spring Security開放了配置,允許我們自定義AuthenticationEntryPoint。
 * 那麼我們就透過自定義AuthenticationEntryPoint來取消重定向行為,將介面改為返回JSON資訊。
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(
            HttpServletRequest request, HttpServletResponse response, AuthenticationException authException
    ) throws IOException, ServletException {
        CommonResult commonResult = new CommonResult();
        ObjectMapper objectMapper = new ObjectMapper();
        commonResult.setCode(YIXGResultEnum.LOGIN_INVALID.getCode())
                    .setMessage(YIXGResultEnum.LOGIN_INVALID.getMessage());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
        response.getWriter().flush();
        response.getWriter().close();
    }
}

攔截沒許可權的請求:

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(
            HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException
    ) throws IOException, ServletException {
        ObjectMapper objectMapper = new ObjectMapper();
        CommonResult commonResult = new CommonResult();
        commonResult.setCode(YIXGResultEnum.NO_PERMISSION.getCode())
                    .setMessage(YIXGResultEnum.NO_PERMISSION.getMessage());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
        response.getWriter().flush();
        response.getWriter().close();
    }
}

自定義攔截器,驗證token資訊:

public class MyAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain
    ) throws ServletException, IOException {
        //  從header中獲取驗證資訊
        String authHeader = request.getHeader(GlobalUtil.AUTHORIZATION);
        if (ObjectUtils.isEmpty(authHeader)) {
            filterChain.doFilter(request, response);
            return;
        }
        this.doParse(request, response, filterChain, authHeader);
    }
 
    private void doParse(
            HttpServletRequest request, HttpServletResponse response, FilterChain chain, String authHeader
    ) throws ServletException, IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        //  如果認證碼  以規定值開頭
        if (authHeader.startsWith(GlobalUtil.GRANT_TYPE)) {
            // 提取token值
            String token = authHeader.substring(GlobalUtil.GRANT_TYPE.length());
            if (ObjectUtils.isEmpty(token)) {
                chain.doFilter(request, response);
                return;
            }
            //  透過token值從快取中取使用者資訊
            String userJson = (String) LocalCache.getInstance().getValue(token);
            //  轉換JSON物件
 
            //JSONObject userJsonObject = JSON.parseObject(userJson);
            //  判斷是否空值
            if (ObjectUtils.isEmpty(userJson)) {
//                throw new CommonException(YIXGResultEnum.INVALID_TOKEN.getCode(),
//                        YIXGResultEnum.INVALID_TOKEN.getMsg());
                chain.doFilter(request, response);
                return;
            }
            //  轉換MyUserDetail物件
            MyUserDetail user = objectMapper.readValue(userJson, MyUserDetail.class);
            //MyUserDetail user = JSON.toJavaObject(userJsonObject, MyUserDetail.class);
//                MyUserDetail user = JSONObject.toJavaObject(userJsonObject, MyUserDetail.class);
            //  轉換 UP 物件放到上下文中
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(
                            user, user.getPassword(), user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
        chain.doFilter(request, response);
    }
}

密碼加密:

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return MD5Util.md5((String) rawPassword);
    }
 
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equalsIgnoreCase(MD5Util.md5((String) rawPassword));
    }
}

登入成功:

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private final ObjectMapper objectMapper;
 
    private final LogService logService;
 
    private final UserService userService;
 
    public MyAuthenticationSuccessHandler(
            ObjectMapper objectMapper, LogService logService,
            UserService userService
    ) {
        this.objectMapper = objectMapper;
        this.logService = logService;
        this.userService = userService;
    }
 
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authentication
    ) throws IOException, ServletException {
        AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
    }
 
    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response, Authentication authentication
    ) throws IOException, ServletException {
        MyUserDetail user = (MyUserDetail) authentication.getPrincipal();
        //  獲取隨機token 並存到Redis中
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        LocalCache.getInstance().putValue(token, objectMapper.writeValueAsString(user), 60 * 60);
        UserVO userVO = new UserVO();
        userVO.setUserName(user.getUserName())
              .setUserErrCount("0")
              .setUserLastErrTime(null);
        userService.updateUserErrCount(userVO);
 
        LogVO logVO = new LogVO();
        logVO.setLogOperateUser(user.getUserName())
             .setLogContent("登入成功")
             .setLogType("登入日誌");
        logService.addLog(logVO);
 
        CommonResult commonResult = new CommonResult();
        commonResult.setCode(YIXGResultEnum.OPERATE_SUCCESS.getCode())
                    .setMessage(YIXGResultEnum.OPERATE_SUCCESS.getMessage())
                    .setToken(token)
                    .setCurrentUser(user.getUserName())
                    .setCurrentUserId(user.getUserID());
 
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
        response.getWriter().flush();
        response.getWriter().close();
    }
}

登入失敗:

@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private final ObjectMapper objectMapper;
 
    private final UserService userService;
 
    public MyAuthenticationFailureHandler(ObjectMapper objectMapper, UserService userService) {
        this.objectMapper = objectMapper;
        this.userService = userService;
    }
 
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request, HttpServletResponse response, AuthenticationException exception
    ) throws IOException, ServletException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        CommonResult result = userService.getUserByUserName(
                new UserVO().setUserName(request.getParameter("username")));
        Users users = (Users) result.getObjectData();
 
        if (Objects.equals(result.getCode(), YIXGResultEnum.OPERATE_SUCCESS.getCode())) {
            UserVO userVO = new UserVO();
            userVO.setUserName(users.getUserName())
                  .setUserErrCount(String.valueOf((users.getUserErrCount() + 1)))
                  .setUserLastErrTime(sdf.format(new Date()));
            userService.updateUserErrCount(userVO);
        }
        
        CommonResult commonResult = new CommonResult();
        commonResult.setCode(YIXGResultEnum.PASSWORD_OR_USERNAME_ERROR.getCode())
                    .setMessage(YIXGResultEnum.PASSWORD_OR_USERNAME_ERROR.getMessage());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
        response.getWriter().flush();
        response.getWriter().close();
    }
}

登出成功:

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    private final ObjectMapper objectMapper;
 
    private final LogService logService;
 
    public MyLogoutSuccessHandler(ObjectMapper objectMapper, LogService logService) {
        this.objectMapper = objectMapper;
        this.logService = logService;
    }
 
    @Override
    public void onLogoutSuccess(
            HttpServletRequest request, HttpServletResponse response, Authentication authentication
    ) throws IOException, ServletException {
        String authHeader = request.getHeader(GlobalUtil.AUTHORIZATION);
        String authToken = authHeader.substring(GlobalUtil.GRANT_TYPE.length());
 
        String userJson = (String) LocalCache.getInstance().getValue(authToken);
        if (ObjectUtils.isEmpty(userJson)) {
            CommonResult commonResult = new CommonResult();
            commonResult.setCode(YIXGResultEnum.OPERATE_FAILURE.getCode())
                        .setMessage(YIXGResultEnum.OPERATE_FAILURE.getMessage());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(commonResult));
            response.getWriter().flush();
            response.getWriter().close();
            return;
        }
        
        MyUserDetail user = objectMapper.readValue(userJson, MyUserDetail.class);
 
        LocalCache.getInstance().putValue(authToken, "", 1);
 
        LogVO logVO = new LogVO();
        logVO.setLogOperateUser(user.getUserName())
             .setLogContent("登出成功")
             .setLogType("登入日誌");
        logService.addLog(logVO);
 
        CommonResult commonResult = new CommonResult();
        commonResult.setCode(YIXGResultEnum.OPERATE_SUCCESS.getCode())
                    .setMessage(YIXGResultEnum.OPERATE_SUCCESS.getMessage());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
        response.getWriter().flush();
        response.getWriter().close();
    }
}

Security核心配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
 
    private final MyAuthenticationFailureHandler myAuthenticationFailureHandler;
 
    private final MyLogoutSuccessHandler myLogoutSuccessHandler;
 
    private final UserDetailsService userDetailsService;
 
    public SecurityConfig(
            MyAuthenticationSuccessHandler myAuthenticationSuccessHandler,
            MyAuthenticationFailureHandler myAuthenticationFailureHandler,
            MyLogoutSuccessHandler myLogoutSuccessHandler, UserDetailsService userDetailsService
    ) {
        this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
        this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;
        this.myLogoutSuccessHandler = myLogoutSuccessHandler;
        this.userDetailsService = userDetailsService;
    }
 
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration
    ) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MyPasswordEncoder();
    }
 
    @Bean
    public MyAuthenticationTokenFilter myAuthenticationTokenFilter() {
        return new MyAuthenticationTokenFilter();
    }
 
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                //  禁用basic明文驗證
                .httpBasic(Customizer.withDefaults())
                //  基於 token ,不需要 csrf
                .csrf(AbstractHttpConfigurer::disable)
                //  禁用預設登入頁
                .formLogin(fl -> fl.loginProcessingUrl("/login")
                                   .usernameParameter("username")
                                   .passwordParameter("password")
                                   .successHandler(myAuthenticationSuccessHandler)
                                   .failureHandler(myAuthenticationFailureHandler)
                                   .permitAll())
                //  禁用預設登出頁
                .logout(lt -> lt.logoutSuccessHandler(myLogoutSuccessHandler))
                //  基於 token , 不需要 session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //  設定 處理鑑權失敗、認證失敗
                .exceptionHandling(
                        exceptions -> exceptions.authenticationEntryPoint(new MyAuthenticationEntryPoint())
                                                .accessDeniedHandler(new MyAccessDeniedHandler())
                )
                //  下面開始設定許可權
                .authorizeHttpRequests(authorizeHttpRequest -> authorizeHttpRequest
                        //  允許所有 OPTIONS 請求
                        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        //  允許直接訪問 授權登入介面
                        .requestMatchers(HttpMethod.POST, "/web/authenticate").permitAll()
                        //  允許 SpringMVC 的預設錯誤地址匿名訪問
                        .requestMatchers("/error").permitAll()
                        //  其他所有介面必須有Authority資訊,Authority在登入成功後的UserDetailImpl物件中預設設定“ROLE_USER”
                        //.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
                        .requestMatchers("/heartBeat/**", "/main/**").permitAll()
                        //  允許任意請求被已登入使用者訪問,不檢查Authority
                        .anyRequest().authenticated()
                )
                //  新增過濾器
                .addFilterBefore(myAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .build();
    }
 
    @Bean
    public UserDetailsService userDetailsService() {
        return userDetailsService::loadUserByUsername;
    }
 
    /**
     * 呼叫loadUserByUserName獲取userDetail資訊,在AbstractUserDetailsAuthenticationProvider裡執行使用者狀態檢查
     *
     * @return
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }
 
//    @Bean
//    public WebSecurityCustomizer webSecurityCustomizer() {
//        return (web) -> web.ignoring().requestMatchers();
//    }
 
    /**
     * 配置跨源訪問(CORS)
     *
     * @return
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
 
}

轉: https://blog.csdn.net/qq_40107343/article/details/136086463

相關文章