一、认证过程
1、编写自定义的过滤器TokenLoginFilter,继承UsernamePasswordAuthenticationFilter,重写
attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication方法,attemptAuthentication方法获取前端提交的用户名和密码,封装成Authentication对象。
successfulAuthentication方法认证成功时调用,unsuccessfulAuthentication认证失败时调用。
package com.atguigu.filter;
import com.atguigu.entity.SecurityUser;
import com.atguigu.entity.User;
import com.atguigu.security.TokenManager;
import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
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 authManager;
public TokenLoginFilter(AuthenticationManager authManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.authManager = authManager;
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 authManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} 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.createToken(user.getCurrentUserInfo().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类,实现UserDetailsService接口,重写loadUserByUsername
方法(从数据库中根据用户名查询用户密码、用户权限等),封装成User对象返回。
package com.atguigu.aclservice.service.impl;
import com.atguigu.aclservice.entity.User;
import com.atguigu.aclservice.service.PermissionService;
import com.atguigu.aclservice.service.UserService;
import com.atguigu.entity.SecurityUser;
import com.atguigu.security.DefaultPasswordEncoder;
import com.atguigu.utils.utils.MD5;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private DefaultPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.selectByUsername(username);
if (user==null){
throw new UsernameNotFoundException("用户名不存在");
}
com.atguigu.entity.User curUser=new com.atguigu.entity.User();
BeanUtils.copyProperties(user,curUser);
List<String> permissionList=permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser=new SecurityUser();
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(permissionList);
return securityUser;
}
}
3、查看源码中DaoAuthenticationProvider类,继承AbstractUserDetailsAuthenticationProvider,additionalAuthenticationChecks方法将前端用户提交的密码和数据库中查询的密码进行匹配,匹配相同即为认证成功。
4、若认证成功,调用TokenLoginFilter中successfulAuthentication方法,若认证失败,调用TokenLoginFilter中unsuccessfulAuthentication方法,将结果返回给前端。
2、授权过程
1、自定义TokenAuthFilter类,继承BasicAuthenticationFilter类,重写doFilterInternal方法,
根据token获取到用户信息,再从redis中查询用户权限,进行授权,放行。
package com.atguigu.filter;
import com.atguigu.security.TokenManager;
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 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 request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authRequest=getAuthentication(request);
if (authRequest!=null){
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
String token=request.getHeader("token");
if (token!=null){
String username = tokenManager.getUserInfoFromToken(token);
List<String> permissionList = (List<String>) redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authorities=new ArrayList<>();
for (String permission:permissionList){
SimpleGrantedAuthority auth= new SimpleGrantedAuthority(permission);
authorities.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authorities);
}
return null;
}
}
3、代码逻辑如下图所示