springboot集成security框架实现登陆授权

135 阅读3分钟

引言

一般业务开发中, 我们会新建一个网关系统用于对接移动端相关的接口。比如H5,小程序,APP。在对接的过程中首先需要解决的问题就是实现多种登陆方式。 比如用户名密码,手机验证码登陆,微信授权登陆,人脸识别登陆等。 目前主流做法就是使用Security,Shiro成熟的框架进行实现。 接下来就来总结相关实现步骤。

环境准备

  • maven依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

  • application.yml
jwt:
  secret: my_secret_2019 #  签名密钥
  expiration: 604800  # jwt有效期(一天:86400,一周 604800)

代码方案

  • JwtFilter
import com.loan.help.crm.modules.user.model.AuthUser;
import com.loan.help.crm.security.wechat.WeChatAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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;

/**
 * 这个类在配置文件已经完成了容器的注入SecurityConfig
 * @author mark1xie
 */
//@Component
@Slf4j
public class JwtAuthFilter extends OncePerRequestFilter {


    @Autowired
    private JwtUtil jwtUtil;

    private String tokenHeader = "Authorization";

    private String tokenPrefix = "Bearer";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws ServletException, IOException {

        // 从http头部读取jwt
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(tokenPrefix)) {
            // The part after "Bearer "
            final String authToken = authHeader.substring(tokenPrefix.length() + 1);
            String username = null, role = null;
            // 从jwt中解出账号与角色信息
            try {
                Long start = System.currentTimeMillis();
                username = jwtUtil.getUsernameFromToken(authToken);
                log.info("username cost:{}", System.currentTimeMillis() - start);
                role = jwtUtil.getClaimFromToken(authToken, "role", String.class);
            } catch (Exception e) {
                log.debug("异常详情", e);
                log.info("无效token");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("{\"status\":403,\"message\":\"无效token\"}");
                return;
            }

            // 如果jwt正确解出账号信息,说明是合法用户,设置认证信息,认证通过
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                long start = System.currentTimeMillis();
//                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
//                        username, null, AuthUser.getAuthoritiesByRole(role));
                WeChatAuthenticationToken auth = new WeChatAuthenticationToken(username, AuthUser.getAuthoritiesByRole(role));

                // 把请求的信息设置到UsernamePasswordAuthenticationToken details对象里面,包括发请求的ip等
                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // 设置认证信息
                SecurityContextHolder.getContext().setAuthentication(auth);
                log.info("auth cost:{}", System.currentTimeMillis() - start);
            }
        }
        // 调用下一个过滤器
        chain.doFilter(request, response);
    }
}
  • SecurityConfig
import com.loan.help.crm.security.jwt.JwtAuthError;
import com.loan.help.crm.security.jwt.JwtAuthFilter;
import com.loan.help.crm.security.sms.MobileCodeAuthenticationProvider;
import com.loan.help.crm.security.wechat.WeChatAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

/**
 * @author mark1xie
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider;
    @Autowired
    private WeChatAuthenticationProvider weChatAuthenticationProvider;

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    /**
     * 权限不足错误信息处理,包含认证错误与鉴权错误处理
     */
    @Autowired
    private JwtAuthError myAuthErrorHandler;
    /**
     * 加载用户信息
     */
    @Autowired
    private UserDetailsService myUserDetailsService;


    /**
     * 密码明文加密方式配置
     */
    @Bean
    public PasswordEncoder myEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 获取AuthenticationManager(认证管理器),可以在其他地方使用
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    /**
     * jwt校验过滤器,从http头部Authorization字段读取token并校验
     */
    @Bean
    public JwtAuthFilter myAuthFilter() throws Exception {
        return new JwtAuthFilter();
    }

    /**
     * 认证用户时用户信息加载配置,注入myUserDetailsService
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .authenticationProvider(mobileCodeAuthenticationProvider)
                .authenticationProvider(weChatAuthenticationProvider);
        auth
                .userDetailsService(myUserDetailsService);
    }


    /**
     * 配置http,包含权限配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()

                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                // 设置myUnauthorizedHandler处理认证失败、鉴权失败
                .exceptionHandling().authenticationEntryPoint(myAuthErrorHandler).accessDeniedHandler(myAuthErrorHandler).and()

                // 设置权限
                .authorizeRequests()
                // 授权不需要登录权限的URL
                .antMatchers("/swagger*/**",
                        "/v2/api-docs",
                        "/webjars*//**").permitAll()
                // 需要登录
                .antMatchers("/activity/auth/**").authenticated()
                

                // 需要角色权限
//                .antMatchers("/order/test").hasRole("ADMIN")
                // hasRole("ROLE_admin") = hasAuthority("admin")

                // 除上面外的所有请求全部放开
                .anyRequest().permitAll();

        // 添加JWT过滤器,JWT过滤器在用户名密码认证过滤器之前
        http.addFilterBefore(myAuthFilter(), UsernamePasswordAuthenticationFilter.class);

        // 禁用缓存
//      http.headers().cacheControl();
    }

    /**
     * 配置跨源访问(CORS)
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

}