SpringSecurity与shiro

SpringSecurity

基本概念:

  • AuthenticationManager ,类似于 Shiro 中的 SecurityManager 。

    它是 “表面上” 的做认证和鉴权比对工作的那个人,它是认证和鉴权比对工作的起点。

    ProvierderManager 是 AuthenticationManager 接口的具体实现。

  • AuthenticationProvider ,类似于 Shiro 中的 Authenticator

    它是 “实际上” 的做认证和鉴权比对工作的那个人。从命名上很容易看出,Provider 受 ProviderManager 的管理,ProviderManager 调用 Provider 进行认证和鉴权的比对工作。

    我们最常用到 DaoAuthenticationProvider 是 AuthenticationProvider 接口的具体实现。

  • UserDetailService ,类似于 Shiro 中的 Realm 。

    虽然 AuthenticationProvider 负责进行用户名和密码的比对工作,但是它并不清楚用户名和密码的『标准答案』,而标准答案则是由 UserDetailService 来提供。简单来说,UserDetailService 负责提供标准答案 ,以供 AuthenticationProvider 使用。

  • UserDetails,类似于 Shiro 中的 AuthenticationInfo + AuthorizationInfo

    UserDetails 它是存放用户认证信息和权限信息的标准答案的 “容器” ,它也是 UserDetailService “应该” 返回的内容。和 Shiro 不同的是,Shiro 中是把认证和权限信息分开放的,而 UserDetails 则是塞到了一起。

  • PasswordEncoder,类似于 Shiro 中的 CredentialsMatcher 。

    Spring Security 要求密码不能是明文,必须经过加密器加密。这样,AuthenticationProvider 在做比对时,就必须知道『当初』密码时使用哪种加密器加密的。所以,AuthenticationProvider 除了要向 UserDetailsService 『要』用户名密码的标准答案之外,它还需要知道配套的加密算法(加密器)是什么。

SpringSecurity的执行流程:

 实现登录验证:

package cn.woniu.security;

import cn.woniu.dao.RolesPermissionsMapper;
import cn.woniu.dao.UserRolesMapper;
import cn.woniu.dao.UsersMapper;
import cn.woniu.domain.*;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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.Component;

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

@Component
public class SimpleUserDetailsService implements UserDetailsService {

    @Resource
    private UsersMapper usersMapper;

    @Resource
    private UserRolesMapper userRolesMapper;

    @Resource
    private RolesPermissionsMapper permissionsMapper;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询登录对象//
        UsersExample usersExample = new UsersExample();
        usersExample.createCriteria().andUsernameEqualTo(username);
        List<Users> usersList = usersMapper.selectByExample(usersExample);
        Users user = usersList.get(0);

        //查询角色集合
        UserRolesExample userRolesExample = new UserRolesExample();
        //通过账号查询角色
        userRolesExample.createCriteria().andUsernameEqualTo(user.getUsername());
        List<UserRoles> userRoles = userRolesMapper.selectByExample(userRolesExample);

        if (userRoles.size() == 0) {
            throw new RuntimeException("没有该角色!");
        }

        StringBuilder str = new StringBuilder();

        userRoles.forEach(role -> {
            //查询该角色的权限
            RolesPermissionsExample rolesPermissionsExample = new RolesPermissionsExample();
            rolesPermissionsExample.createCriteria().andRoleNameEqualTo(role.getRoleName());
            //查询权限列表
            List<RolesPermissions> rolesPermissions = permissionsMapper.selectByExample(rolesPermissionsExample);

            str.append("ROLE_" + role.getRoleName().toUpperCase());

            str.append(",");
            rolesPermissions.forEach(RolesPermissions -> {
                str.append(RolesPermissions.getPermission());
                str.append(",");
            });
        });
        String rolesPermission = str.substring(0, str.length() - 1);
//        System.out.println(rolesPermission);
        //查询用户的权限
//    usersMapper.selectByExample();
        return new User(user.getUsername(), user.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(rolesPermission));
    }
}

 

Spring整合JWT流程:

认证:

@Component
public class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final HashOperations<String, String, String> operations;

    // 构造注入
    public JWTAuthenticationSuccessHandler(RedisTemplate<String, String> redisTemplate) {
        this.operations = redisTemplate.opsForHash();
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {

        // authentication 对象携带了当前登陆用户名等相关信息
        User user = (User) authentication.getPrincipal();
        resp.setContentType("application/json;charset=UTF-8");
        try {
            StringBuffer buffer = new StringBuffer();
            user.getAuthorities().forEach(item -> {
                buffer.append(item.getAuthority());
                buffer.append(",");
            });
            buffer.deleteCharAt(buffer.length()-1);

            // 用户的 username 和他所具有的权限存入 redis 中。
            operations.put(JWTUtil.REDIS_HASH_KEY, user.getUsername(), buffer.toString());

            // 在 jwt-token-string 的荷载(payload)中存上当前用户的名字和所具有的权限。
            String jwtStr = JWTUtil.createJWT(user.getUsername());

            Map<String, String> map = new HashMap<>();
            map.put("code", "10000");
            map.put("msg", "login success");
            map.put("jwt-token", jwtStr);

            PrintWriter out = resp.getWriter();
            out.write(objectMapper.writeValueAsString(map));
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

 鉴权:

1、从网关中拿出jwtToken中的账号

@Slf4j
@Component
public class GateWayFilter implements GlobalFilter {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //拿出请求头中的token
        List<String> jwtToken = exchange.getRequest().getHeaders().get("jwt-token");
        //登录请求或是其他没带token的请求直接放行

        //校验token是否正确
        if (jwtToken != null && jwtToken.size() > 0 && JWTUtil.verify(jwtToken.get(0))) {
            //从jwtToken中解析出username
            String username = JWTUtil.getUsernameFromJWT(jwtToken.get(0));
            ServerHttpRequest host = exchange.getRequest().mutate().header("x-username", username).build();

            return chain.filter(exchange.mutate().request(host).build());
        }

        return chain.filter(exchange);
    }
}

2、在鉴权微服务中进行鉴权

package cn.woniu.xxxservice.security.filter;


import cn.woniu.xxxservice.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;

/**
 * Spring Security 建议我们直接继承 OncePerRequestFilter 类,间接实现 Filter 接口。
 * 它能确保我们的 Filter 在一次请求过程中,仅执行一次。
 */
@Slf4j
@Component
public class JwtFilter extends OncePerRequestFilter {

    private final HashOperations<String, String, String> operations;

    // 构造注入
    public JwtFilter(RedisTemplate<String, String> redisTemplate) {
        this.operations = redisTemplate.opsForHash();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. 如果 Spring Security Context 中已经有了一个 Authentication Token,那么就意味着,这个请求不归“我”管。无条件放行。
        Authentication token = SecurityContextHolder.getContext().getAuthentication();
        if (token != null) {
            log.info("上下文中已存在 Authentication Token");
            filterChain.doFilter(request, response);
            return;
        }

        String username = request.getHeader("x-username");

        //获取AuthoritiesStr 权限字符串
        String authorities = operations.get("jwt-token", username);


        log.info("{} - {} - {}", username, null, authorities);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }

}

 

posted @ 2021-12-03 19:12  *山山_而川  阅读(115)  评论(0)    收藏  举报