java 设计

spingboot springsecurity 集成 jwt(

2019-11-07  本文已影响0人  楚长铭

依赖

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.security:spring-security-test'
    implementation 'com.github.ladyishenlong:response-utils:1.0'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
}

用户信息

@Data
@Component
public class Student {

   public String username = "123";
    public String password = "456";
    public String verificationcode = "789"; //验证码
    public String secret = "secretKey"; // token的密钥
    public Set<GrantedAuthority> authorities; //用户权限

    public Set<GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(new SimpleGrantedAuthority("root"));
        authorities.add(new SimpleGrantedAuthority("admin"));
        return authorities;
    }
}

spring security配置

这里先说几句,用jwt进行用户验证其实并不难,自己写个过滤器或者拦截器总能搞定,但麻烦的是如何与spring security框架进行集成,原因自然是因为spring security框架原本使用的是用户名,密码以及session进行登录验证的,于是需要对这些验证的地方进行改造才行

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private LoginUserDetailsService loginUserDetailsService;


    @Override
    protected void configure(HttpSecurity http) throws Exception {

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

        http
                .cors()
                .and()
                .formLogin().disable()
                .csrf().disable()//禁用csrf防护
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//关闭session

                .and()

                //配置请求的权限
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/test").permitAll()

                //需要特定用户的权限
                .antMatchers("/test3").access(
                        "@AuthService.role('bigboss',request)")
                //普通的请求
                .anyRequest().access("@AuthService.auth(request)")

                .and()
                .authenticationProvider(getLoginAuthProvider())
                .httpBasic()

                .and()
                .exceptionHandling()
                //未授权处理
                .authenticationEntryPoint(new UnAuthorizedEntryPoint())

                .and()
                .addFilterBefore(new UserAuthFilter("/login", authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class)
        ;

    }


    @Bean
    public LoginAuthProvider getLoginAuthProvider() {
        //采用该方式初始化,在LoginAuthProvider中除了构造函数之外可以依赖注入
        return new LoginAuthProvider(loginUserDetailsService);
    }
    
}

登录请求

 .and()
                .addFilterBefore(new UserAuthFilter("/login", authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class)
@Slf4j
public class UserAuthFilter extends UsernamePasswordAuthenticationFilter {

    private static final String VERIFICATION_CODE = "verificationcode";//验证码

    public UserAuthFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
        setFilterProcessesUrl(defaultFilterProcessesUrl);
        setAuthenticationManager(authenticationManager);
    }


    /**
     * 登录接口,用户粗
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(
            HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        String username = obtainUsername(request);//用户名
        String password = obtainPassword(request);//密码
        String verificationcode = obtainVerificationCode(request);//验证码

        if (StringUtils.isEmpty(username)) username = "";
        if (StringUtils.isEmpty(password)) password = "";
        if (StringUtils.isEmpty(verificationcode)) verificationcode = "";

        UserAuthToken userAuthToken = new UserAuthToken(username, password, verificationcode);

        //将post请求传入的用户信息放入
        return getAuthenticationManager().authenticate(userAuthToken);
    }


    /**
     * 登录请求成功返回的的信息
     * 在这里返回的是token
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain, Authentication authResult) throws IOException, ServletException {

        UserModel userModel = (UserModel) (authResult.getPrincipal());

        //生成token
        String token = TokenUtils.createToken(userModel.getUsername(),
                userModel.getSecret(),
                userModel.getAuthorities());

        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(
                ResponseUtils.success("登录成功", token)));
        out.flush();
        out.close();
    }


    /**
     * 登录请求失败返回的信息
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        //也可以设置401状态码
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(
                ResponseUtils.failure(failed.getMessage())));
        out.flush();
        out.close();
    }


    @Nullable
    protected String obtainVerificationCode(HttpServletRequest request) {
        return request.getParameter(VERIFICATION_CODE);
    }

@Data
public class UserAuthToken extends UsernamePasswordAuthenticationToken {

    private Object verificationcode;

    public UserAuthToken(Object principal, Object credentials, Object verificationcode) {
        super(principal, credentials);
        this.verificationcode = verificationcode;
    }

    public UserAuthToken(Object principal) {
        super(principal, "");
    }

}

查询数据库中的用户信息

@Slf4j
@Component
public class LoginUserDetailsService implements UserDetailsService {

    @Autowired
    private Student student;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //密码需要加密
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        //TODO 如果用户没有被查询到,可以直接抛出 UsernameNotFoundException
        //TODO 数据库查询 获取  用户名 密码 secret 权限 验证码 等信息
        //TODO spring security 框架默认用户名密码,如果使用验证码方式,直接在后台写死一个固定密码,否则会有问题

        //测试环境 写死一个用户信息
        return new UserModel(student.getUsername(), encoder.encode(student.getPassword()),
                student.getSecret(), student.getVerificationcode(), student.getAuthorities());
    }
}
@Data
public class UserModel extends User {


    private String secret;
    private String verificationcode;

    public UserModel(String username, String password, String secret, String verificationcode,
                     Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.secret = secret;
        this.verificationcode = verificationcode;
    }


}

验证登录

@Slf4j
public class LoginAuthProvider extends DaoAuthenticationProvider {


    public LoginAuthProvider(LoginUserDetailsService loginUserDetailsService) {
        super();
        setUserDetailsService(loginUserDetailsService);//必须设置
    }


    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

        //todo 还能做一些非空的判断

        UserModel userModel = (UserModel) userDetails;//从数据库查出来的参数
        UserAuthToken userAuthToken = (UserAuthToken) authentication;//登录请求携带的参数

        if (!getPasswordEncoder().matches(userAuthToken.getCredentials().toString(),
                userModel.getPassword()))
            throw new BadCredentialsException("密码错误");

        if (!userAuthToken.getVerificationcode()
                .equals(userModel.getVerificationcode()))
            throw new BadCredentialsException("验证码错误");

        log.info("验证用户:{},{}", userModel, userAuthToken);
    }
}

spring security配置说明

UserAuthFilter //将/login中的用户名密码传入框架,同时定义了登录成功 和登录失败的方法

LoginUserDetailsService//通过用户名从数据库查询用户信息

LoginAuthProvider //比对上面两个类的信息,校验用户
  .and()
                .addFilterBefore(new UserAuthFilter("/login", authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class)
 @Autowired
private LoginUserDetailsService loginUserDetailsService;


@Bean
public LoginAuthProvider getLoginAuthProvider() {
        //采用该方式初始化,在LoginAuthProvider中除了构造函数之外可以依赖注入
        return new LoginAuthProvider(loginUserDetailsService);
 }

.and()
.authenticationProvider(getLoginAuthProvider())

普通请求

  .and()
                .exceptionHandling()
                //未授权处理
                .authenticationEntryPoint(new UnAuthorizedEntryPoint())
public class UnAuthorizedEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {

        int code = AuthExceptionCode.getCode(authException.getMessage());
        String reason = AuthExceptionCode.getReason(authException.getMessage());
        if (code == 0) reason = authException.getMessage();

        WriteUtils.writeJson(response, ResponseUtils.failure(code, reason, null));
    }

}
 //配置请求的权限
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/test").permitAll()

                //需要特定用户的权限
                .antMatchers("/test3").access(
                        "@AuthService.role('bigboss',request)")
                //普通的请求
                .anyRequest().access("@AuthService.auth(request)")
@Slf4j
@Component(value = "AuthService")
public class AuthService {


    @Autowired
    private Student student;
    

    /**
     * 普通请求认证
     *
     * @param request
     * @return
     * @throws AuthenticationException
     */
    public boolean auth(HttpServletRequest request)
            throws AuthenticationException {
        String token = request.getHeader(TokenUtils.AUTHORIZATION);
        //解析密钥是后台查询的
        Claims claims = TokenUtils.parserToken(token, student.getSecret());
        //用户名
        String username = claims.getSubject();
        UserAuthToken userAuthToken = new UserAuthToken(username);
        //设置Context
        SecurityContextHolder.getContext()
                .setAuthentication(userAuthToken);
        return true;
    }


    /**
     * 单个 用户权限验证
     *
     * @param role
     * @param request
     * @return
     */
    public boolean role(String role, HttpServletRequest request) {
        String token = request.getHeader(TokenUtils.AUTHORIZATION);
        //解析密钥是后台查询的
        Claims claims = TokenUtils.parserToken(token, student.getSecret());

        //用户名
        String username = claims.getSubject();
        //权限
        List<LinkedHashMap<String, String>> authorities =
                (List<LinkedHashMap<String, String>>) (claims.get("authorities"));

        boolean hasRole = false;
        for (LinkedHashMap<String, String> authority : authorities) {
            if (authority.get("authority").equals(role)) {
                hasRole = true;
                break;
            }
        }

        if (!hasRole) throw new BadCredentialsException("没有访问该接口的权限");


        UserAuthToken userAuthToken = new UserAuthToken(username);
        SecurityContextHolder.getContext()
                .setAuthentication(userAuthToken);
        return true;
    }


}

token生成与解析

  public static String createToken(String username, String secret,
                                     Collection<GrantedAuthority> authorities) {
        return Jwts.builder()
                .setSubject(username)
                .claim(AUTHORITIES, authorities)//配置用户权限(角色)
                .setIssuedAt(DateUtils.createDate()) //设置token发布时间
                .setExpiration(DateUtils.expirationDate(TIME_OUT))//设置过期时间
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }



    public static Claims parserToken(String token, String secret) throws AuthenticationException {
        try {
            return Jwts.parser().setSigningKey(secret)
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            throw new BadCredentialsException(AuthExceptionCode.EXPIRED.getCodeValue());
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException(AuthExceptionCode.EMPTY.getCodeValue());
        } catch (SignatureException e) {
            throw new BadCredentialsException(AuthExceptionCode.SIGN.getCodeValue());
        } catch (MalformedJwtException e) {
            throw new BadCredentialsException(AuthExceptionCode.MALFORMED.getCodeValue());
        } catch (UnsupportedJwtException e) {
            throw new BadCredentialsException(AuthExceptionCode.UNSUPPORTED.getCodeValue());
        }catch (Exception e){
            throw new BadCredentialsException(AuthExceptionCode.UNKNOW.getCodeValue());
        }
    }

上一篇 下一篇

猜你喜欢

热点阅读