认证授权过程分析

(1)如果是基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找 到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。

(2)如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限 信息中去

微服务中spring security的运用 spring security 微服务认证_redis

 

 

 

准备工作

目录结构

微服务中spring security的运用 spring security 微服务认证_java_02

 

 

工具类

MD5加密工具

package com.wang.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public final class MD5 {

public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}

public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}

}

 

统一结果返回值

package com.wang.utils;

public interface ResultCode {

    public static Integer SUCCESS = 20000;
    public static Integer ERROR = 20001;
}
package com.wang.utils;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;

@Data
public class R {

  @ApiModelProperty(value = "是否成功")

  private Boolean success;


  @ApiModelProperty(value = "返回码")

  private Integer code;


  @ApiModelProperty(value = "返回消息")

  private String message;


  @ApiModelProperty(value = "返回数据")

  private java.util.Map<String, Object> data = new HashMap<String, Object>();


  private R(){}


  public static R ok(){

      R r = new R();

      r.setSuccess(true);

      r.setCode(ResultCode.SUCCESS);

      r.setMessage("成功");

      return r;

  }


  public static R error(){

      R r = new R();

      r.setSuccess(false);

      r.setCode(ResultCode.ERROR);

      r.setMessage("失败");

      return r;

  }


  public R success(Boolean success){

      this.setSuccess(success);

      return this;

  }


  public R message(String message){

      this.setMessage(message);

      return this;

  }


  public R code(Integer code){

      this.setCode(code);

      return this;

  }


  public R data(String key, Object value){

      this.data.put(key, value);

      return this;

  }


  public R data(java.util.Map<String, Object> map){

      this.setData(map);

      return this;

  }

}

 

请求响应工具类

package com.wang;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.utils.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ResponseUtil {

    public static void out(HttpServletResponse response, R r) {
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 全局异常处理

package com.wang.exceptionhandler;

import com.wang.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e){
        e.printStackTrace();
        return R.error().message("执行了全局异常!!!");

    }

    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public R error(ArithmeticException e){
        e.printStackTrace();
        return R.error().message("执行了ArithmeticException异常!!!");

    }

    @ExceptionHandler(WangException.class)
    @ResponseBody
    public R error(WangException e){
        log.error(e.getMessage());
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());

    }

}

自定义异常处理

package com.wang.exceptionhandler;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WangException extends RuntimeException{
    @ApiModelProperty(value = "状态码")
    private Integer code;
    private String msg;
}

 swagger测试

package com.wang;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                //.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }

    private ApiInfo webApiInfo(){

        return new ApiInfoBuilder()
                .title("网站-课程中心API文档")
                .description("本文档描述了课程中心微服务接口定义")
                .version("1.0")
                .contact(new Contact("java", "http://wang.com", "1123@qq.com"))
                .build();
    }
}

 

 

redis配置

package com.wang;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

}

准备工作结束

 

springsecurity需要的一些类

目录结构

微服务中spring security的运用 spring security 微服务认证_spring_03

 

 

密码处理的方法

package com.wang.security.security;

import com.wang.utils.MD5;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
//密码处理的方法
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    public DefaultPasswordEncoder() {
        this(-1);
    }
    public DefaultPasswordEncoder(int a) {

    }

    @Override
    public String encode(CharSequence charSequence) {

        return MD5.encrypt(charSequence.toString());
    }

    @Override
    public boolean matches(CharSequence charSequence, String encodepassword) {

        return encodepassword.equals(MD5.encrypt(charSequence.toString()));
    }
}

退出处理类

package com.wang.security.security;

import com.wang.ResponseUtil;
import com.wang.utils.R;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TokenLogoutHandler implements LogoutHandler {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate ){
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;


    }
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader("token");
        if (token!=null)
        {
            String username = tokenManager.getUserFromToken(token);
            redisTemplate.delete(token);

        }
        ResponseUtil.out(response, R.ok());





    }
}

token 操作的工具类

package com.wang.security.security;

import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Date;
//token 操作的工具类
@Component

public class TokenManager {

    private long tokenExpiration=1000*60*60*24;//设置过期时间
    private String tokenSignKey="123456";//加密字符串
//生成token
    private String createToken(String username){

        String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)
        ).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    //根据token得到用户信息
    public  String getUserFromToken(String token)
    {
        String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return user;


    }

}

 未授权统一处理

package com.wang.security.security;

import com.wang.ResponseUtil;
import com.wang.utils.R;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//未授权统一处理
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(httpServletResponse, R.error());
    }
}

 

 

 正式开始

spring接收前端信息的实体类

package com.wang.security.entity;

import io.swagger.annotations.ApiModel;
import lombok.Data;

import java.io.Serializable;

@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
    private String username;
    private String password;
    private String nickName;
    private String salt;
    private String token;

}

 

springsecurity需要接管的实现类UserDetails的实体对象 securityUser,用来封装用户请求信息和权限

package com.wang.security.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * <p>
 * 安全认证用户详情信息
 * </p>
 *
 * @author qy
 * @since 2019-11-08
 */
@Data
@Slf4j
public class SecurityUser implements UserDetails {

    //当前登录用户
    private transient User currentUserInfo;

    //当前权限
    private List<String> permissionValueList;

    public SecurityUser() {
    }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for(String permissionValue : permissionValueList) {
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }

        return authorities;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.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;
    }
}

 

 

 认证过程

微服务中spring security的运用 spring security 微服务认证_java_04

 

 

 

源码分析

1.

微服务中spring security的运用 spring security 微服务认证_spring_05

 

2.查看过滤器的父类AbstractAuthenticationProcessingFilter

 

 

 

 

 

 

 

 

微服务中spring security的运用 spring security 微服务认证_spring_06

 

微服务中spring security的运用 spring security 微服务认证_spring_07

 

 

 

微服务中spring security的运用 spring security 微服务认证_java_08

 

 

 

 

微服务中spring security的运用 spring security 微服务认证_spring_09

 

 

 上述的 第二 过程调用了 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法,源码如下:

微服务中spring security的运用 spring security 微服务认证_spring_10

 

 

 

微服务中spring security的运用 spring security 微服务认证_spring_11

 

 

 

微服务中spring security的运用 spring security 微服务认证_spring_12

 

 

 上述的(3)过程创建的 UsernamePasswordAuthenticationToken 是 Authentication 接口的实现类,该类有两个构造器,一个用于封装前端请求传入的未认 证的用户信息,一个用于封装认证成功后的用户信息:

微服务中spring security的运用 spring security 微服务认证_redis_13

 

 

 Authentication 接口的实现类用于存储用户认证信息,查看该接口具体定义:

微服务中spring security的运用 spring security 微服务认证_java_14

 

 

 ProviderManager 源码

上述过程中,UsernamePasswordAuthenticationFilter 过滤器的 attemptAuthentication() 方法的(5)过程将未认证的 Authentication 对象传入 ProviderManager 类的 authenticate() 方法进行身份认证。ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接 口,也是认证的入口。在实际开发中,我们可能有多种不同的认证方式,例如:用户名+ 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。在该接口的常用实现类 ProviderManager 内部会维护一个 List列表,存放多种认证方式,实际上这是委托者模式 (Delegate)的应用。每种认证方式对应着一个 AuthenticationProvider, AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托 对应的 AuthenticationProvider 进行用户认证。

微服务中spring security的运用 spring security 微服务认证_redis_15

 

 

 

微服务中spring security的运用 spring security 微服务认证_java_16

 

 

 

 

 

 

 代码实现

 

1.首先进入TokenLoginFilter(继承了UsernamePasswordAuthenticationFilter)的attemptAuthentication()方法进行信息认证,需要一个实现了UserDetails的实体对象,在UserDetailsServiceImpl的loadUserByUsername方法返回值可以得到,通过实现了UserDetails的实体对象 securityUser可以将前端传过来的用户信息与数据库查到的用户信息比较

package com.wang.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.ResponseUtil;
import com.wang.security.entity.SecurityUser;
import com.wang.security.entity.User;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import io.jsonwebtoken.Jwts;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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.ArrayList;

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    private AuthenticationManager authenticationManager;//spring来验证用户信息

    public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.authenticationManager = authenticationManager;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);//获取前端请求的对象
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));//封装用户信息
//实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }


    }

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

        //认证成功,等得到认证成功之后的用户信息
        SecurityUser user = (SecurityUser) authResult.getPrincipal();
        String token = tokenManager.getUserFromToken(user.getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        ResponseUtil.out(response, R.ok().data("token",token));

    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}

 

 

 

2.从权限认证模块UserDetailsServiceImpl的loadUserByUsername方法获取用户登录的信息,封在UserDetails的实现类中的,把信息封装成springsecurity需要的user类型,设置给UserDetails的实现类securityUser,再查询到权限信息,设置给UserDetails的实现类securityUser

package com.wang.aclservice.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.wang.aclservice.entity.User;
import com.wang.aclservice.service.PermissionService;
import com.wang.aclservice.service.UserService;
import com.wang.security.entity.SecurityUser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;


import javax.annotation.Resource;
import java.util.List;

/**
 * @author lixiaolong
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private UserService userService;

    @Resource
    private PermissionService permissionService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据
        User user = userService.selectByUsername(username);
        //判断
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        com.wang.security.entity.User curUser = new com.wang.security.entity.User();//查询出来的对象赋值给security的对象,spring要认证
        BeanUtils.copyProperties(user, curUser);//保持请求的类型与security要接受的一致,security接受的是自定义的

        //根据用户查询用户权限列表
        List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());

        SecurityUser securityUser = new SecurityUser();//curUser
        securityUser.setCurrentUserInfo(curUser);

        securityUser.setPermissionValueList(permissionValueList);//获取用户权限 设置给security,返回UserDetails实现类 交给springsecurity
        return securityUser;
    }
}

 

 

3.认证成功之后到进入TokenLoginFilter的successfulAuthentication()方法得到用户的当前信息(包括在第2步中设置的用户信息和权限信息),然后生成token,将用户名和权限以key:value设置到redis中,将token返回前端

认证完毕

package com.wang.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.ResponseUtil;
import com.wang.security.entity.SecurityUser;
import com.wang.security.entity.User;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import io.jsonwebtoken.Jwts;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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.ArrayList;

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    private AuthenticationManager authenticationManager;//spring来验证用户信息

    public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.authenticationManager = authenticationManager;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);//获取前端请求的对象
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));//封装用户信息
//实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }


    }

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

        //认证成功,等得到认证成功之后的用户信息
        SecurityUser user = (SecurityUser) authResult.getPrincipal();
        String token = tokenManager.getUserFromToken(user.getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        ResponseUtil.out(response, R.ok().data("token",token));

    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}

 

授权

SpringSecurity 权限访问流程

ExceptionTranslationFilter 过滤器 该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后 续抛出的异常并进行处理(例如:权限访问限制)。具体源码如下:

微服务中spring security的运用 spring security 微服务认证_java_17

 

 

 

FilterSecurityInterceptor 过滤器 FilterSecurityInterceptor 是过滤器链的最后一个过滤器,该过滤器是过滤器链 的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果 访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器 ExceptionTranslationFilter 进行捕获和处理。具体源码如下

微服务中spring security的运用 spring security 微服务认证_redis_18

 

 

微服务中spring security的运用 spring security 微服务认证_redis_19

 

 需要注意,Spring Security 的过滤器链是配置在 SpringMVC 的核心组件 DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器, 不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链。

 

 

1.从前端head中得到token信息,然后用jwt根据token获取用户名 再从redis中查询权限,给当前用户授权查到的权限信息

package com.wang.security.filter;

import com.wang.ResponseUtil;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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.ArrayList;
import java.util.Collection;
import java.util.List;

public class TokenAuthFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenAuthFilter(AuthenticationManager authenticationManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authenticationManager);
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;



    }

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

        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }

        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
            //实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);

            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }
            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }


}