谷粒学院项目——SpringSecurity模块总结

谷粒学院项目——SpringSecurity模块总结,第1张

谷粒学院项目——SpringSecurity模块总结

安全框架(springsecurity、shiro等)主要分为两个部分:

  • 认证:系统认为用户是否能登录
  • 授权:系统判断用户是否有权限去做某些事情

项目主要使用了:基于token的用户权限认证与授权

  •  模块一:登录时springsecurity获取用户的信息,比如用户名、密码、和查数据库得到其权限列表
如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式 进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限 值,并以用户名为 key ,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息 生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带 到 header 请求头中, Spring-security 解析 header 头获取 token 信息,解析 token 获取当前 用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前 请求是否有权限访问

 项目后台系统权限管理模块说明

  • 用户管理:表示可以登录这个后台网站的用户

  •  角色管理:不同的角色可以访问不同的权限菜单集合,要给每个用户分配一种角色


 权限管理数据库模型:

  • 菜单表acc_permission

  • 一共有两级菜单,一级菜单的pid为1,二级菜单的pid为一级菜单的id 
  •  菜单角色关系表acl_role_permission
  • 维护菜单和角色的关系

 

  • 用户角色关系表acl_user_role
  • 维护用户和角色之间的关系 

 


JWT工具类 
  • 项目中用户登录成功后,需要根据用户名生成token,放在cookie中,jwt就是生成token的工具。当前端给服务器发送请求时,请求头会携带token信息,服务器通过校验token,来确定该用户的权限
  • token就是一种服务器颁发的访问令牌,服务器会设置签名算法和密钥,通过签名算法和密钥以及token的有效载荷,生成token的签名,保证数据不会被篡改。

 

 

  •  签名哈希

注意在权限管理模块使用的JWT工具类是:但是这个工具类比较简单,我就拿common_utils中的JWT工具类来说明!!!!!!

 

public class JwtUtils {

    public static final long EXPIRE = 1000 * 60 * 60 * 24;//设置token的过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//秘钥加密

    //生成token字符串的方法(根据用户的id和昵称生成字符串)
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")//设置令牌类型为JWT令牌
                .setHeaderParam("alg", "HS256")//设置签名算法为HS256
                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置token的过期时间
                //claim是token的主体部分
                .claim("id", id)
                .claim("nickname", nickname)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET) //设置签名算法和密钥
                .compact();

        return JwtToken;
    }

    
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");//把token放在request的头信息中
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    
    //解析token,得到用户id
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");//从请求头中获取token字符串
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

 



springsecurity4个工具类(这一部分具体直接看代码注释)


认证、授权过滤器(具体看代码注释)

  • 认证过滤器
      认证的过程是,先从登录请求中拿到对应的用户名密码,对用户名密码进行登录校验(与数据库中比对),认证成功后执行successfulAuthentication方法,通过JWT工具类根据用户名生成token放在客户端的cookie中然后在redis中加入一个key,value,分别是用户名和权限列表认证失败则在响应中返回错误信息。

  • 授权过滤器

        当一个用户已经登录后,下次再访问服务器时,请求头中包含token信息,

        服务器先从header中获取token,解析得到用户名,从redis中获取权限列表,由springsecurity给当前用户赋予权限*(哪些可以访问,哪些不能访问)

  •  使用redisTemplate *** 作redis

service_acl模块 UserDetailsServiceImpl类
  • 因为在springsecurity包的核心配置类中要使用userDetailsService接口的实现类来 *** 作数据库,查询用户的用户名密码等数据

  • 因此 service_acl模块中要有这个实现类——UserDetailsServiceImpl类
  • @Service("userDetailsService")//该bean对象的引用名就是userDetailsService
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private PermissionService permissionService;
    
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 从数据库中取出用户信息
            User user = userService.selectByUsername(username);
    
            // 判断用户是否存在
            if (null == user){
                //throw new UsernameNotFoundException("用户名不存在!");
            }
            
            // 返回UserDetails实现类
            com.atguigu.serurity.entity.User curUser = new com.atguigu.serurity.entity.User();
            BeanUtils.copyProperties(user,curUser);
            
            //查询用户的权限列表
            List authorities = permissionService.selectPermissionValueByUserId(user.getId());
            
            
            SecurityUser securityUser = new SecurityUser(curUser);
            securityUser.setPermissionValueList(authorities);
            return securityUser;
        }
    
    }

 service_acl其他部分:
  • 其他部分主要是对这五张表进行增删改查 *** 作


api_gateway模块:  CorsConfig:解决跨域问题
@Configuration
public class CorsConfig {
    //解决跨域问题,有这个类后,其他模块的controller就不用加@CrossOrigin注解了
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("acl
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

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

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());

        ResponseUtil.out(res, R.ok().data("token", token));
    }

    
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
  •  打开UsernamePasswordAuthenticationFilter源码,有下面这个方法
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        
        //1、先判断是不是post提交
        if (this.postonly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {


        //2、从请求request获取用户名和密码
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }
            if (password == null) {
                password = "";
            }
            username = username.trim();

        //3、查数据库,对用户名和密码进行校验
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  1.  先判断是不是post提交
  2. 从请求request获取用户名和密码
  3. 查数据库,对用户名和密码进行校验

过滤器是如何被加载的?
  •  springboot帮我们完成了springsecurity的自动化配置

两个重要的接口: UserDetails
  • 项目中SecurityUser实现了这个接口

  •  SecurityUser
@Data
@Slf4j
public class SecurityUser implements UserDetails {

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

    //当前权限
    private List permissionValueList;

    public SecurityUser() {
    }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }
    // 表示获取登录用户所有权限
    @Override
    public Collection getAuthorities() {
        Collection 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;
    }
}
  • UserDetails 源码:
public interface UserDetails extends Serializable {
    // 表示获取登录用户所有权限
    Collection getAuthorities();
    // 表示获取密码
    String getPassword();
    // 表示获取用户名
    String getUsername();
    // 表示判断账户是否过期
    boolean isAccountNonExpired();
    // 表示判断账户是否被锁定
    boolean isAccountNonLocked();
    // 表示凭证{密码}是否过期
    boolean isCredentialsNonExpired();
    // 表示当前用户是否可用
    boolean isEnabled();
}
  • 简而言之,UserDetails就是用来获取用户的一些具体信息的 
PasswordEncoder
  • 密码需要加密,不是明文
  • 项目中DefaultPasswordEncoder实现了这个接口

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

    public DefaultPasswordEncoder() {
        this(-1);
    }

    
    public DefaultPasswordEncoder(int strength) {

    }
    //使用MD5加密密码
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}
  1. 项目中通过MD5算法加密
  2. matches方法: 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹 配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。

 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5694895.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存