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);
}
}

浙公网安备 33010602011771号