国家开放大学网站界面设计,建设银行网站修改密码,织梦手机端网站字体重叠,口碑好的盘锦网站建设一、初步理解 SpringSecurity的原理其实就是一个过滤器链#xff0c;内部包含了提供各种功能的过滤器。 当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。 核心过滤器#xff1a;
#xff08;认证#xff09;UsernamePasswordAuthenticationFilter:负责处理…一、初步理解 SpringSecurity的原理其实就是一个过滤器链内部包含了提供各种功能的过滤器。 当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。 核心过滤器
认证UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求ExceptionTranslationFilter处理过滤器链中抛出的任何AccessDeniedException和 AuthenticationException 授权FilterSecurityInterceptor负责权限校验的过滤器
二、TokenJwt登录校验流程 三、具体认证授权细节
下图是UsernamePasswordAuthenticationFilter处理用户名、密码然后将用户名、密码、权限信息封装到Authentication对象中再放到SecurityContextHolder中。 Authentication接口: 它的实现类表示当前访问系统的用户封装了用户相关信息。
AuthenticationManager接口定义了认证Authentication的方法
UserDetailsService接口加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的 方法。
UserDetails接口提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装 成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
认证
当用户登录时前端将用户输入的用户名、密码信息传输到后台后台用一个类对象将其封装起来通常使用的是UsernamePasswordAuthenticationToken这个类。程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中比较两者的密码如果密码正确就成功登陆同时把包含着用户的用户名、密码、所具有的权限等信息用户id、昵称、是否管理员的类对象放到SecurityContextHolder安全上下文容器类似Session中去。用户访问一个资源的时候首先判断是否是受限资源。如果是的话还要判断当前是否未登录没有的话就跳到登录页面。如果用户已经登录访问一个受限资源的时候程序要根据url去数据库中取出该资源所对应的所有可以访问的角色然后拿着当前用户的所有角色一一对比判断用户是否可以访问这里就是和权限相关。
授权
在SpringSecurity中会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。然后设置我们的资源所需要的权限即可。 自定义登录认证接口①调用ProviderManager的方法进行认证②如果认证通过生成jwt③把用户信息存入redis中 自定义权限信息查询在UserDetailsService这个实现类中去查询数据库 四、自定义权限查询
修改UsernamePasswordAuthenticationFilter上图最右边的授权部分。 1.自定义登陆接口
RestController
RequestMapping(/user)
public class UserController {Autowiredprivate UserService userService;PostMapping(/login)public R login(RequestBody User user) {String jwt userService.login(user);if (StringUtils.hasLength(jwt)) {return R.ok().message(登陆成功).data(token, jwt);}return R.error().message(登陆失败);}
} 2.配置数据库校验登录用户
从之前的分析我们可以知道我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的 UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
创建一个类实现UserDetailsService接口重写loadUserByUsername方法
Service
public class UserDetailsServiceImpl implements UserDetailsService {Autowiredprivate UserMapper userMapper;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息QueryWrapperUser queryWrappernew QueryWrapper();queryWrapper.eq(user_name,username);User user userMapper.selectOne(queryWrapper);//如果没有查询到用户就抛出异常if(Objects.isNull(user)){throw new RuntimeException(用户名或密码错误);}//TODO 查询用户对应的权限信息细节见SpringSecurity二——授权实现//如果有把数据封装成UserDetails对象返回return new LoginUser(user);}
}
五、Jwt认证过滤器自定义过滤器 1在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在 SecurityConfig中配置把AuthenticationManager注入容器。
EnableWebSecurity
Configuration
EnableGlobalMethodSecurity(prePostEnabled true)
public class SecurityConfig{/*** 登录时需要调用AuthenticationManager.authenticate执行一次校验*/Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
} 2登录的业务逻辑层实现类
第一次登录生成jwt存入redis
Service
public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService {Autowiredprivate AuthenticationManager authenticationManager;Autowiredprivate StringRedisTemplate stringRedisTemplate;Overridepublic String login(User user) {//1.封装Authentication对象 ,密码校验自动完成UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());//2.进行校验Authentication authenticate authenticationManager.authenticate(authentication);//3.如果authenticate为空if (Objects.isNull(authenticate)) {throw new RuntimeException(登录失败); //TODO 登录失败}//4.得到用户信息LoginUser loginUser (LoginUser) authenticate.getPrincipal();//生成jwt使用fastjson的方法把对象转成字符串String loginUserString JSON.toJSONString(loginUser);//调用JWT工具类生成jwt令牌String jwt JwtUtils.createJWT(loginUserString, null);//5.把生成的jwt存到redisString tokenKey token_ jwt;stringRedisTemplate.opsForValue().set(tokenKey, jwt, JwtUtils.JWT_TTL / 1000);MapString, Object map new HashMap();map.put(token, jwt);map.put(username, loginUser.getUsername());return jwt;}
}
3jwt认证校验过滤器
我们需要自定义一个过滤器这个过滤器会去获取请求头中的token对token进行解析取出其中的 userid。 使用userid去redis中获取对应的LoginUser对象。
然后封装Authentication对象存入SecurityContextHolder
/*** token验证过滤器 //每一个servlet请求只会执行一次*/
Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {Autowiredprivate LoginFailureHandler loginFailureHandler;Autowiredprivate StringRedisTemplate stringRedisTemplate;Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {try {//1.获取当前请求的url地址String url request.getRequestURI();//如果当前请求不是登录请求则需要进行token验证if (!url.equals(/user/login)) {//2.验证tokenthis.validateToken(request);}} catch (AuthenticationException e) {System.out.println(e);loginFailureHandler.onAuthenticationFailure(request, response, e);}//3.登录请求不需要验证tokendoFilter(request, response, filterChain);}/*** 验证token*/private void validateToken(HttpServletRequest request) throws AuthenticationException {//1.获取tokenString token request.getHeader(Authorization);//如果请求头部没有获取到token则从请求的参数中进行获取if (ObjectUtils.isEmpty(token)) {token request.getParameter(Authorization);}if (ObjectUtils.isEmpty(token)) {throw new CustomerAuthenticationException(token不存在);}//2.redis进行校验String redisStr stringRedisTemplate.opsForValue().get(token_ token);if(ObjectUtils.isEmpty(redisStr)) {throw new CustomerAuthenticationException(token已过期);}//3.解析tokenClaims claims null;try {claims JwtUtils.parseJWT(token);} catch (Exception e) {throw new CustomerAuthenticationException(token解析失败);}//4.获取到用户信息String loginUserString claims.getSubject();//把字符串转成loginUser对象LoginUser loginUser JSON.parseObject(loginUserString, LoginUser.class);//创建身份验证对象UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//5.设置到Spring Security上下文SecurityContextHolder.getContext().setAuthentication(authenticationToken);}
}
4把jwt过滤器注册到springsecurity过滤器链中
放在UsernamePasswordAuthenticationFilter前面
EnableWebSecurity
Configuration
EnableGlobalMethodSecurity(prePostEnabled true)
public class SecurityConfig{//自定义jwt校验过滤器Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {//配置关闭csrf机制http.csrf(csrf - csrf.disable());//登陆失败处理器http.formLogin(configurer - {configurer.failureHandler(loginFailureHandler);});http.sessionManagement(configurer -// STATELESS无状态 表示应用程序是无状态的不会创建会话。configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));//请求拦截方式http.authorizeHttpRequests(auth - auth.requestMatchers(/user/login).permitAll().anyRequest().authenticated());//注册jwt过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//异常处理器http.exceptionHandling(configurer - {configurer.accessDeniedHandler(customerAccessDeniedHandler);configurer.authenticationEntryPoint(anonymousAuthenticationHandler);});return http.build(); //允许跨域}/*** 登录时需要调用AuthenticationManager.authenticate执行一次校验*/Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
}