上一篇做好基础操作之后现在开始做最终开发,首先完成登录位置:
登录板块
首先关注AuthenticationProvider类,spring security用户认证主要是通过AuthenticationManager这个类完成的,但是但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider,所以我们要使用jwt配合security而又不通过security默认登录来做登录的时候,就需用户自己来完成认证工作了
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.bcrypt.BCryptPasswordEncoder;
public class CustomAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public CustomAuthenticationProvider(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder){
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException,DisabledException {
// 获取认证的用户名 & 密码
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// 认证逻辑
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
//登录异常全部在JWTLoginFilter中统一抛出
if (null != userDetails) {
if (bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
if (!userDetails.isAccountNonLocked()){
throw new DisabledException("当前账户被锁定了,请联系管理员");
}else{
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, userDetails.getAuthorities());
return auth;
}
} else {
throw new BadCredentialsException("密码错误");
}
} else {
throw new UsernameNotFoundException("用户不存在");
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
这里主要是进行authenticate方法的重写,所有登录中的异常全部向上抛出,因为这里只是做认证动作,登录最终完成还是得去UsernamePasswordAuthenticationFilter这个类完成
首先UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter针对使用用户名和密码进行身份验证而定制化的一个过滤器
当用户登录得时候调用这个过滤器,底层自动调用AuthenticationProvider去认证可以配合完成登录认证工作
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysecurity.liuyf.pojo.User;
import com.mysecurity.liuyf.util.ResponseUtil;
import com.mysecurity.liuyf.util.ResultCode;
import com.mysecurity.liuyf.util.jwt.AuthException;
import com.mysecurity.liuyf.util.jwt.JwtUtil;
import com.mysecurity.liuyf.util.jwt.TokenException;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
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.*;
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private JwtUtil jwtUtil;
public JWTLoginFilter(AuthenticationManager authenticationManager,JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.jwtUtil=jwtUtil;
}
// 尝试身份认证(接收并解析用户凭证)
@SneakyThrows
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
Map<String, Object> data = new HashMap<>();
try {
User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
Authentication authentication= authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
new ArrayList<>())
);
return authentication;
} catch (IOException e) {
System.out.println(e);
throw new RuntimeException(e);
}catch (Exception e) {
//登录异常再这里捕获并且返回给前端数据
// System.out.println(e.getMessage());
data.put("code", ResultCode.LOGIN_FAIL.val());
data.put("message", e.getMessage());
ResponseUtil.sendMsg(res,data);
return null;
}
}
// 认证成功(用户成功登录后,这个方法会被调用,我们在这个方法里生成token)
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
Map<String, Object> data = new HashMap<>();
// builder the token
String token = null;
try {
//添加用户权限到token中去,动态修改权限的话不能立即生效
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
// 定义存放角色权限集合的对象
StringBuilder sb=new StringBuilder();
Object[] grantes=authorities.toArray();
for(int i=0;i<grantes.length;i++){
sb.append(grantes[i]);
if(i!=grantes.length-1){
sb.append(",");
}
}
token= jwtUtil.generateToken(auth.getName()+"-"+sb.toString());
// System.out.println(token);
// 登录成功后,返回token到header里面
data.put("code", ResultCode.SUCCESS.val());
data.put("data", token);
ResponseUtil.sendMsg(response,data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里需要重写两个方法
attemptAuthentication方法尝试身份认证(接收并解析用户凭证)
successfulAuthentication这个方法,这个方法主要是登录认证成功并且加载了用户的资料后调用的,我们在这个位置使用jwtUtil.generateToken()创建了token(这里把权限也写进去了)
这样登录的整体就完成了,接收用户登录数据,调用security验证数据,最后把所需要的数据写到token中回传给前端(注意格式),让前端人员保存到客户端用于客户端访问的时候携带
权限认证板块
登录模块写好了,我们需要构造权限拦截、访问认证板块了
BasicAuthenticationFilter:主要做security的权限认证,我们需要编写类继承它
import com.mysecurity.liuyf.util.ResponseUtil;
import com.mysecurity.liuyf.util.ResultCode;
import com.mysecurity.liuyf.util.jwt.JwtUtil;
import com.mysecurity.liuyf.util.jwt.TokenException;
import io.jsonwebtoken.*;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
public class JwtTokenInterceptor extends BasicAuthenticationFilter {
private JwtUtil jwtUtil;
public JwtTokenInterceptor(AuthenticationManager authenticationManager,JwtUtil jwtUtil) {
super(authenticationManager);
this.jwtUtil=jwtUtil;
}
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, TokenException {
Map<String, Object> data = new HashMap<>();
String header = request.getHeader("token");
if (header == null|| header.isEmpty()) {
data.put("code", ResultCode.PARAMS_ERROR.val());
data.put("message", ResultCode.PARAMS_ERROR.msg());
ResponseUtil.sendMsg(response,data);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);
if (authentication!=null){
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
Map<String, Object> data = new HashMap<>();
data.put("code", ResultCode.SYS_ERROR.val());
String token = request.getHeader("token");
// parse the token.
String user = null;
try {
// 密钥
Claims claims = jwtUtil.parseToken(token);
/* // token签发时间
long issuedAt = claims.getIssuedAt().getTime();
// 当前时间
long currentTimeMillis = System.currentTimeMillis();
// token过期时间
long expirationTime = claims.getExpiration().getTime();*/
//System.out.println(claims);
user = claims.getSubject();
if (user != null) {
String[] split = user.split("-")[1].split(",");
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
for (int i=0; i < split.length; i++) {
authorities.add(new SimpleGrantedAuthority(split[i]));
}
return new UsernamePasswordAuthenticationToken(user, null, authorities);
}
}
/**
* 捕获并返回前端token相关的异常
*/
catch (ExpiredJwtException e) {
data.put("message", "Token已经过期");
ResponseUtil.sendMsg(response,data);
return null;
}
catch (MalformedJwtException|UnsupportedJwtException|IllegalArgumentException|SignatureException e ) {
data.put("message", "Token格式错误");
ResponseUtil.sendMsg(response,data);
return null;
}
catch (Exception e) {
System.out.println(e);
data.put("message", ResultCode.SYS_ERROR);
ResponseUtil.sendMsg(response,data);
return null;
}
return null;
}
}
重写doFilterInternal方法去验证
UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);这个位置我们调用getAuthentication()方法,这这个方法中完成token的解析以及对当前token权限的解析(之前登录的时候随着用户名一起放置在jwt中,在这个位置反向解析出来,完成new UsernamePasswordAuthenticationToken()交给security去处理权限)
在getAuthentication方法中我们抛出token错误(乱写的token)和token过期的异常用于返回给前端
最后编写一个类继承自AccessDeniedHandler用于当用户没有某种权限的时候返回前端错误
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysecurity.liuyf.util.JsonResult;
import com.mysecurity.liuyf.util.ResultCode;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
// TODO Auto-generated method stub
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
ObjectMapper mapper = new ObjectMapper();
String result=null;
result= mapper.writeValueAsString(new JsonResult(ResultCode.USER_NO_PERMISSION,ResultCode.USER_NO_PERMISSION.msg()));
response.getWriter().print(result);
}
}
这样我们基础都写完了,最后我们完成配置
@Configuration
//@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 权限不足
* @return
*/
@Bean
AccessDeniedHandler customAccessDeniedHandler(){
return new MyAccessDeniedHandler();
}
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
/**
* 身份认证位置
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder()));
}
@Autowired
ApplicationParams params;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(params.getAntMatchers());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/userLogin").anonymous()
.antMatchers(HttpMethod.GET,"/*.html").permitAll()
.antMatchers("/sayHello").hasAnyAuthority("user:add")
.antMatchers("/del").hasAnyAuthority("user:del")
.antMatchers("/admin/say").hasRole("ADMIN")
.antMatchers("/say").hasRole("SALE_ADMIN")
.anyRequest().authenticated().and()
.addFilter(new JWTLoginFilter(authenticationManager(),jwtUtil))
.addFilter(new JwtTokenInterceptor(authenticationManager(),jwtUtil))
.csrf().disable();
//用于权限不足位置
http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler());
}
}
这里是做最后的配置,需要注意的是
.addFilter(new JWTLoginFilter(authenticationManager(),jwtUtil))
.addFilter(new JwtTokenInterceptor(authenticationManager(),jwtUtil))
这两个filter一个是加载登录位置,一个是权限认证位置
这样整体就写完了我们来测试一下,接下一篇

本文详细介绍了如何使用Spring Security和JWT实现登录认证。首先讲解了AuthenticationProvider在用户认证中的作用,然后展示了自定义的CustomAuthenticationProvider类,用于处理登录逻辑。接着,介绍了UsernamePasswordAuthenticationFilter的自定义实现JWTLoginFilter,该过滤器处理用户登录请求并生成JWT token。最后,讨论了权限验证的实现,包括BasicAuthenticationFilter的子类JwtTokenInterceptor,用于解析和验证JWT token。文章还提到了权限不足时的处理类MyAccessDeniedHandler,并给出了完整的配置类SecurityConfig。
1440

被折叠的 条评论
为什么被折叠?



