(个人理解,如果有不对的地方,还请大佬指教)
认证
认证流程
源码分析
前端登录后将请求被UserNamePasswordAuthenticationFilter拦截,后将请求中的用户信息给UsernamePasswordAuthenticationToken封装,后传给authenticationManager进行认证,ProviderManager实现了它,交给ProviderManager来验证,然后给AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider去验证,DaoAuthenticationProvider的retrieveUser方法去调用UserDetailsService接口的loadUserByUsername方法,我们自己创建方法UserDetailsServiceImpl实现了UserDetailsService,自定义查找数据库(等验证登录信息),返回userDatils,我们创建方法LoginUser实现userDatils接口,返回查到的用户信息
返回用户信息后,通过加密的方法判断登录信息是否正确,BCryptPasswordEncoder实现了PasswordEncoder接口,判断信息一致,认证成功
具体实现
创建过滤器代替UserNamePasswordAuthenticationFilter(因为用户信息要从前端请求中拿,默认的获取不了)
//自定义 filter
public class LoginKaptchaFilter extends UsernamePasswordAuthenticationFilter {
public static final String FORM_KAPTCHA_KEY = "kaptcha";
private String kaptchaParameter = FORM_KAPTCHA_KEY;
public String getKaptchaParameter() {
return kaptchaParameter;
}
public void setKaptchaParameter(String kaptchaParameter) {
this.kaptchaParameter = kaptchaParameter;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
try {
//1.获取请求数据
Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
String kaptcha = userInfo.get(getKaptchaParameter());//用来获取数据中验证码
String username = userInfo.get(getUsernameParameter());//用来接收用户名
String password = userInfo.get(getPasswordParameter());//用来接收密码
//2.获取 session 中验证码
String sessionVerifyCode = (String) request.getSession().getAttribute("kaptcha");
if (!ObjectUtils.isEmpty(kaptcha) && !ObjectUtils.isEmpty(sessionVerifyCode) &&
kaptcha.equalsIgnoreCase(sessionVerifyCode)) {
//3.获取用户名 和密码认证
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
} catch (IOException e) {
e.printStackTrace();
}
throw new KaptchaNotMatchException("验证码不匹配!");
}
}
//登录接口(与创建过滤器代替UserNamePasswordAuthenticationFilter原理一样)
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
System.out.println(authenticate);
//判断是否认证通过
if(Objects.isNull(authenticate)){
throw new RuntimeException("用户名或密码错误");
}
//获取userid 生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//把用户信息存入redis
redisCache.setCacheObject("login:"+userId,loginUser);
//把token封装 返回
Map<String,String> map = new HashMap<>();
map.put("token",jwt);
return ResponseResult.okResult(map);
}
//自定义数据源
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//判断是否查到用户 如果没查到抛出异常
if(Objects.isNull(user)){
throw new RuntimeException("用户不存在");
}
//返回用户信息
if(user.getType().equals(SystemConstants.ADMAIN)){
List<String> list = menuMapper.selectPermsByUserId(user.getId());
return new LoginUser(user,list);
}
return new LoginUser(user,null);
}
}
//实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
}
授权
授权流程
在没有配置情况下,默认是可以访问所有地址
授权相对认证比较简单,
基于方法
实例
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true, jsr250Enabled=true)
public class SecurityConfig extends WebsecurityConfigurerAdapter{}
@PreAuthorize("hasRole('ADMIN') and authentication.name=='root'")
@GetMapping
public String hello() {
return "hello";
}
public class User implements UserDetails {
private Integer id;
private String password;
private String username;
private boolean enabled;
private boolean locked;
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());
}
....
}
这里user在认证的时候已经设置的权限(角色),在方法上面加注解判断hasRole(‘角色’)时进入上图源码,判断 ’角色‘ 是否被包含在用户所拥有的角色中来判断能否访问方法。
- @PostAuthorize: 在目前标方法执行之后进行权限校验。
- @PostFiter: 在目标方法执行之后对方法的返回结果进行过滤。
- @PreAuthorize:在目标方法执行之前进行权限校验。
- @PreFiter:在目前标方法执行之前对方法参数进行过滤。
- @Secured:访问目标方法必须具各相应的角色。
- @DenyAll:拒绝所有访问。
- @PermitAll:允许所有访问。
- @RolesAllowed:访问目标方法必须具备相应的角色。
基于url
//实现接口去自定义判断是否有权限
@Component
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private final MenuService menuService;
@Autowired
public CustomSecurityMetadataSource(MenuService menuService) {
this.menuService = menuService;
}
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
List<Menu> allMenu = menuService.getAllMenu();
for (Menu menu : allMenu) {
if (antPathMatcher.match(menu.getPattern(), requestURI)) {
String[] roles = menu.getRoles().stream().map(r -> r.getName()).toArray(String[]::new);
return SecurityConfig.createList(roles);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
//更改认证时的数据源,并把用户角色查出来,方便权限验证
@Service
public class UserService implements UserDetailsService {
private final UserMapper userMapper;
@Autowired
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(userMapper.getUserRoleByUid(user.getId()));
return user;
}
}