【安全框架】springSecurity

参考项目:

**主要功能:**用户认证(就是用户名密码验证我们系统有这个用户)、用户授权(就是判断这个用户是不是有这个权限)

需要实现:

1、登录

2、token生成及验证,保存管理等等

3、角色管理体系

4、ip白名单

配置文件:

需要重写实现的地方:

解决的问题:

1、根据deploy.mode配置,决定是省端的青天模式,门户模式,还是地市自己的用户系统模式

配置以下注解
@ConditionalOnProperty(name={"deploy.mode"}, havingValue = "system")
就是除了模式是system的时候,其他时候不去创建这个bean就可以实现

了解:

主体就是这个config文件,通过配置一些过滤器,来实现用户校验、ip限制啥的

1、web拦截

这边配置了html、ico、css等文件的访问

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2、配置拦截器

每个filter怎么拦截的,可以看下面附录的代码备注

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3、框架的密码加密校验

自定义一个加密方式,继承于PasswordEncoder,重写match方法,再controller方法中指定使用这种判断方式

public class MD5PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return Md5Util.getMd5(charSequence.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!encodedPassword.equalsIgnoreCase(encode(rawPassword))) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
}

4、重写UserDetailServiceImpl类的loadUserByUsername

就是根据用户名获取用户信息返回用户

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser dbUsers = getDbUser(username);
        if (!username.equals(dbUsers.getUsername())) {
            throw new UsernameNotFoundException(String.format("该用户不存在 '%s'.", username));
        }
        SecurityUser securityUser = null;
        try {
            securityUser = new SecurityUser(dbUsers);
        } catch (InvocationTargetException | IllegalAccessException e) {
            logger.error("构建security用户失败", e);
        }
        if(securityUser == null){
            throw new UsernameNotFoundException(String.format("该用户不存在 '%s'.", username));
        }
        if (BaseConstants.FOUR.equals(securityUser.getStatus())) {
            //用户被禁用
            securityUser.setAccountNonLocked(Boolean.FALSE);
        } else if (BaseConstants.TWO.equals(securityUser.getStatus())) {
            //用户被注销
            securityUser.setEnabled(Boolean.FALSE);
        }
        return securityUser;
    }

5、一些安全措施

问题:

1、jwt登录头验证,如果地址在白名单url里面,jwt直接跳过

如果不在白名单,且传的token为空也可以通过

如果不在白名单,且传的token错误则不通过

2、

附录:

1、配置文件:securityConfig

package com.meiya.assistance.web.auth.config;

import com.meiya.assistance.web.auth.encoder.MD5PasswordEncoder;
import com.meiya.assistance.web.auth.encoder.SM4PasswordEncoder;
import com.meiya.assistance.web.auth.filter.JwtAuthorizationTokenFilter;
import com.meiya.assistance.web.auth.properties.IgnoredUrlsProperties;
import com.meiya.assistance.web.auth.service.impl.UserDetailsServiceImpl;
import com.meiya.basic.constant.BaseConstant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;

/**
 * 系统权限配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ConditionalOnProperty(name={"deploy.mode"}, havingValue = "system")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${account.passwordEncode}")
    private String passwordEncodeType;

    @Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    JwtAuthorizationTokenFilter authenticationTokenFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoderBean());
    }

    @Bean
    public PasswordEncoder passwordEncoderBean() {
        // 根据配置判断,是使用哪种加密方式
        if(StringUtils.equals(passwordEncodeType, BaseConstant.MD5)){
            return new MD5PasswordEncoder();
        } else {
            return new SM4PasswordEncoder();
        }
    }

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

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                   HttpMethod.GET,
                   "/*.html",
                   "/favicon.ico",
                   "/**/*.html",
                   "/**/*.css",
                   "/**/*.js"
           );
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                .authorizeRequests();
        // ----------------------------------------1、url白名单,有配置的话这边直接permitAll跳过了-------------------------------------
        //除配置文件忽略路径其它所有请求都需经过认证和授权
        for (String url : ignoredUrlsProperties.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        // ------------------------------------------2、用户token校验,这边叫做Authorization里面可以改的---------
        registry.and()
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // ------------------------------------------3、------------------------------------------------
        // security中启用跨域访问                       !!!因为项目是前后端分离的,所以要解决跨域问题,否则vue访问不了后端,这边就开启了
        												//需要注意的是,为了防止跨站攻击
        registry.and()
                .cors()
                .and()
                .csrf().disable();
        // ------------------------------------------4、------------------------------------------------
        // 禁用security page cache
        registry.and()
                .headers()
                .frameOptions().sameOrigin()
                .cacheControl();
        // 通过添加Content-Security-Policy拦截             !!!xss攻击
        http.headers()
            .xssProtection()
            .and()
            .contentSecurityPolicy("script-src 'self'");
    }
}

2、加密工具

package com.xxxxx.assistance.web.config.security;

import com.xxxxx.basic.constant.BaseConstant;
import com.xxxxx.framework.common.util.Md5Util;
import com.xxxxx.portal.util.sm4.SM4Utils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * SM4加密
 * @Author 
 * @Date 2022/10/14 10:53
 */
public class SM4PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return SM4Utils.sm4EncForCBC(BaseConstant.SM4_DES_KEY, charSequence.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if(StringUtils.isBlank(encodedPassword)){
            return false;
        }
        if (!encodedPassword.equalsIgnoreCase(encode(rawPassword))) {
            return false;
        } else {
            return true;
        }
    }
}

3、jwt过滤器

package com.xxxxx.assistance.web.auth.filter;

import com.xxxxx.assistance.common.cache.LoginCache;
import com.xxxxx.assistance.web.auth.domain.SecurityUser;
import com.xxxxx.assistance.web.auth.properties.IgnoredUrlsProperties;
import com.xxxxx.assistance.web.auth.utils.JwtTokenUtil;
import com.xxxxx.assistance.web.auth.utils.SecurityContextUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
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.List;
import java.util.Objects;

@Component
@Slf4j
@ConditionalOnProperty(name={"deploy.mode"}, havingValue = "system")
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

    @Autowired
    @Qualifier("userDetailService")
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.header}")
    private String  tokenHeader;

    @Autowired
    private LoginCache loginCache;

    @Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException, ExpiredJwtException {
        log.debug("processing authentication for '{}'", request.getRequestURL());

        AntPathMatcher matcher = new AntPathMatcher();
        String uri = request.getRequestURI();
        for (String url : ignoredUrlsProperties.getUrls()) {
            if (matcher.match(url, uri)) {
                chain.doFilter(request, response);
                return;
            }
        }
        final String requestHeader = request.getHeader(this.tokenHeader);

        String username = null;
        String authToken = null;

        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(authToken);
            } catch (ExpiredJwtException e) {
                log.info("token expired:{}", e);
                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED, "token expired");
                return;
            } catch (Exception e) {
                log.info("invalide token:{}", e);
                response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "invalide token");
                return;
            }
        } else {
            log.warn("couldn't find bearer string, will ignore the header");
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            log.debug("security context was null, so authorizating user");

            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            List<GrantedAuthority> authorities = jwtTokenUtil.getAuthorities(authToken);
            // 兼容门户对接的角色设置
            if (!authorities.isEmpty()) {
                ((SecurityUser) userDetails).setAuthorities(authorities);
            }
            //token校验合规则未被挤出
            boolean isValidToken = jwtTokenUtil.validateToken(authToken, userDetails);

            String tokenInCache = loginCache.get(SecurityContextUtil.getLoginKey(username));

            if (StringUtils.isBlank(tokenInCache)) {
                // 没有缓存,表示正常退出
                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED, "has been logout");
                return;
            }
            boolean isExistInCache = Objects.nonNull(authToken) && authToken.equals(tokenInCache);
            if (isValidToken && isExistInCache) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                log.info("authorizated user '{}', setting security context", username);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else if (!isValidToken) {
                response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "invalide token");
                return;
            } else if (isValidToken && !isExistInCache) {
                response.sendError(419, "has been logout");
                return;
            }
        }
        chain.doFilter(request, response);

    }

}
	
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Security 是一个强大且灵活的认证和授权框架,用于保护 Spring Boot 应用程序的安全性。下面是一个简单的入门指南,帮助你开始使用 Spring Security: 1. 添加 Spring Security 依赖:在你的项目的 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 创建一个 Spring Security 配置类:创建一个类并注解为 `@Configuration`,这个类将用于配置 Spring Security。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public").permitAll() // 允许访问公共资源 .anyRequest().authenticated() // 其他请求需要认证 .and() .formLogin() // 启用表单登录 .and() .logout() // 启用注销功能 .and() .csrf().disable(); // 禁用 CSRF(跨站请求伪造)保护 } } ``` 在上述示例中,我们配置了一些基本的安全规则。可以自定义更多的规则来满足你的需求,比如配置自定义登录页面、添加用户角色等。 3. 添加用户认证:在上述配置类中,可以添加一个内存用户存储来进行简单的用户认证。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin") .password("{noop}password") // 使用 {noop} 前缀表示不加密密码 .roles("ADMIN"); } } ``` 在上述示例中,我们创建了一个用户名为 "admin"、密码为 "password"、角色为 "ADMIN" 的用户。 这只是 Spring Security 的入门指南,你可以进一步学习如何使用数据库存储用户信息、配置角色权限等。希望这些信息对你有所帮助!如果你有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值