一,认证(只定义用户和密码)
1.1实现UserDetailsService
重写loadUserByUsername
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired(required = false)
@Lazy
private PasswordEncoder passwordEncoder;
@Autowired(required = false)
private UserDao userDao;
/**
* 根据页面传递的account,至数据库查询信息,存入security中
* @param account 页面的username
*/
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
Users users = userDao.queryAuth(account);
if (!Objects.isNull(users)){
String auths = String.join(",",users.getAuths());
return new User(users.getAccount(),passwordEncoder.encode(users.getPassword()),
AuthorityUtils.commaSeparatedStringToAuthorityList(auths));
}else {
throw new UsernameNotFoundException("用户名或密码错误");
}
}
}
1.2,继承WebSecurityConfigurerAdapter
@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private JWTFilter jwtFilter;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// .loginPage("/login.html")//使用自定义登录界面
// .loginProcessingUrl("/doLogin")//表单提交地址,对应表单Action
.successHandler(loginSuccessHandler)//登录成功
.failureHandler(new LoginFailedHandler())//登录失败
.permitAll();//放开 login.html和dologin 的访问权
http.exceptionHandling()
.accessDeniedHandler(new MyAccessDeniedHandler())//无权限
.authenticationEntryPoint(new MyAuthenticationEntryPointHandler());//未登录
http.authorizeRequests()
.anyRequest().authenticated();//所有请求都被拦截
http.csrf().disable();//关闭跨站脚本攻击
//前后端项目中要禁用掉session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//注册过滤器
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
二,认证(自定义登录页面,非前后端分离项目用)
2.1,配置4个处理类
登录成功:AuthenticationSuccessHandler
登录失败:AuthenticationFailureHandler
未登录访问资源:AuthenticationEntryPoint
没有权限访问资源:AccessDeniedHandler
登录成功代码例子:
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Override
@SneakyThrows
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取用户登录信息,获取jwt字符串
User user = (User) authentication.getPrincipal();
String jwt = JWTUtil.createJWT(user.getUsername());
//将信息存入redis中
redisTemplate.opsForValue()
.set("jwt:"+user.getUsername(),jwt, 30,TimeUnit.MINUTES);
response.setContentType("application/json;charset=utf-8");
PrintWriter pw = response.getWriter();
String success = JSON.toJSONString(new ResponseResult<>().ok(jwt));
pw.print(success);
pw.flush();
pw.close();
}
}
2.2,编写JWT 工具类
/**
* JWT工具类
* 1.创建jwt
* 2.校验JWT合法性
* 3.返回载荷部分
*/
public class JWTUtil {
public static String key = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
/**
* 加密
*/
public static String createJWT(String usernme) throws Exception{
//1.创建jwt头部
JWSHeader jwsHeader = new JWSHeader
.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT)
.build();
//2.创建荷载,保存用户信息
Map userMap = new HashMap();
userMap.put("username",usernme);
Payload payload = new Payload(userMap);
//3.创建签名构造器,
//----3.1将头部与荷载拼接
JWSObject jwsObject = new JWSObject(jwsHeader,payload);
//----3.2根据密钥将jwsObject加密
JWSSigner jwsSigner = new MACSigner(key);
jwsObject.sign(jwsSigner);
return jwsObject.serialize();
}
/**
* 解密判断
* @param jwt
*/
public static boolean decode(String jwt) throws Exception{
//将jwt字符串转成一个jwt对象
JWSObject parse = JWSObject.parse(jwt);
//将对象通过密钥解密
JWSVerifier jwsVerifier = new MACVerifier(key);
return parse.verify(jwsVerifier);
}
/**
* 获取荷载内容
* @param jwt
* @return
*/
public static Map getPayload(String jwt) throws Exception{
JWSObject jwsObject = JWSObject.parse(jwt);
Map payload = jwsObject.getPayload().toJSONObject();
return payload;
}
}
三,security 整合 jwt
继承 OncePerRequestFilter
/**
* 1.判断用户请求是否携带jwt凭证, 是:继续执行下面逻辑,否:放行不处理
* 2.判断携带的jwt是否合法, 是:继续执行,否:放行不处理
* 3.用户携带的jwt与redis的jwt进行对比:
* 3.1根据key判断redis是否存在(key获取方式:解析jwt载荷)
* 3.2判断两个jwt值是否相同
*/
@Component
public class JWTFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = request.getHeader("jwt");
//1.
if (jwt == null) {
filterChain.doFilter(request, response);
return;
}
//2.
if (!JWTUtil.decode(jwt)) {
filterChain.doFilter(request, response);
return;
}
//3.
Map payload = JWTUtil.getPayload(jwt);
String username = (String) payload.get("username");
String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);
if (redisJWT == null) {
filterChain.doFilter(request, response);
return;
}
if (!jwt.equals(redisJWT)) {
filterChain.doFilter(request, response);
return;
}
//token续期
redisTemplate.opsForValue().set("jwt:" + username, jwt, 30, TimeUnit.MINUTES);
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);//放行到下一个过滤器
}
}