SpringSecurity+JWT权限认证

SpringSecurity默认的是采用Session来判断请求的用户是否登录的,但是不方便分布式的扩展
虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态,不过现在分布式的还是无状态的Jwt比较主流

一、创建SpringBoot的项目

spring-boot-starter-web
spring-boot-starter-security

二、代码

server.port=8111# 再配置文件中设置登录系统的账户、密码
spring.security.user.name=jordan
spring.security.user.password=jordan
/**通过配置类设置登录系统的账户、密码
*/
@Configuration
public class SecurityConfig extends WebSecurityConfig{@Override protected void configure(AuthenticationManagerBuilder auth){//密码加密BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = passwordEncoder.encode("123");auth.inMemoryAuthentication().withUser("kobe").password(password).roles("admin");}@BeanPasswordEncoder password(){return new BCryptPasswordEncoder();}
}	
/**自定义配置类,设置用户名、密码该配置类设置使用哪个service实现类
*/
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter{@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(userDetailsService).passwordEncoder(password());}@BeanPasswordEncoder password(){return new BCryptPasswordEncoder();}/**自定义登录页面*/@Overrideprotected void configure(HttpSecurity http) throws Exception{//配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");http.formLogin()//自定义自己编写的页面.loginPage("/login.html")//登录页面设置.loginProcessingUrl("/user/login")//登录访问路径.defaultSuccessUrl("/test/index").permitAll()//登录成功后跳转的路径.and().authorizeRequests().antMatchers("/","test/hello","/user/login").permitAll()//设置哪些路径可以直接访问//	.antMatchers("test/index").hasAuthority("admins")//当前登录用户,只有具有admins角色的权限下才能访问该路径(单个角色).antMatchers("/test/index").hasAnyAuthority("admins,manager")//	多个权限.antMatchers("/test/index").hasRole("sale") //单个角色.antMatchers("index").hasAnyRole("")//多个角色任意一个//.anyRequest().authenticated().and().csrf().disable();//关闭csrf防护}
}/**service实现类
*/
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService{@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{//设置权限List<GrantedAuthority> auths = AuthorityUtils.commaeparatedStringToAuthorityList("admins,ROLE_sale");//设置权限,和角色(角色必须又前缀ROLE_)return new User("james",new BCryptPasswordEncoder().encode("123"),auths);}
}
@RestController
@RequestMapping("/test")
public class TestController{@GetMapping("hello")public String add(){return "hello security";}
}

三、启动运行

通过浏览器访问http://localhost:8111
进入页面 user 默认密码从控制台中寻找(三种方式设置用户名和密码:配置文件方式、配置类、自定义编写实现类

===========================================================

查询数据库完成认证

一、添加依赖

mybatis-plus-boot-starter
mysql-connector-java
lombok

二、创建数据表(id,username,password)以及对应的实体类,配置文件中添加数据库连接信息
在这里插入图片描述
在这里插入图片描述

三、编写mapper,以及service

/**启动类上必须添加该接口所在的包扫描 @MapperScan("com.xxx.mapper")
*/
@Repository
public interface UsersMapper extends BaseMapper<User>{}
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailService{@Autowiredprivate UsersMapper usersMapper;@Overridepublic UserDetails loadUserByUsername throws UsernameNotFoundException(String username){//调用usersMapper,查询数据库QueryWrapper<Users> wrapper = new QueryWrapper();wrapper.eq("username",username);//where username=? Users users = usersMapper.selectOne(wrapper);if(users == null){ //数据库没有用户名,认证失败throw new UsernameNotFoundException("用户名不存在");}List<GrantAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");return new User(users.getUsername(),newBCryptPasswordEncoder().encode(users.getPassword()),auths);}}

=====================================================

注解

一、启动类上开启注解@EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled=true)
public class DemosecurityApplication{}

二、控制类

@RestController
@RequestMapping("/test")
public class TestController{@GetMapping("update")@Secured("ROLE_sale","ROLE_manager")//设置角色public String update(){return "hello update";}
}@RequestMapping("/preAuthorize")
@ResponseBody
@PreAuthorize("hasAnyAuthority('admin')")//进入方法前权限验证,将角色权限参数传到方法中
public String preAuthorize(){System.out.printn("preAuthorize");return "preAuthorize";
}/**@PostAuthorize("hasAnyAuthority('admins')")//方法后权限验证,主要针对返回值
*//**@PostFilter 权限验证之后对数据进行过滤,留下用户名是admin1的数据
*/
@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin'")
@ResponseBody
public List<UserInfo> getAllUser(){ArrayList<UserInfo> list = new ArrayList<>();list.add(new UserInfo(1l,"admin1","6666"));list.add(new UserInfo(2l,"admin2","888"));return list;
}/**@PostFilter 权限验证之后对数据进行过滤,留下用户名是admin1的数据
*/

三、userDetailsService设置用户角色

List<GrantAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_sale");

==============================================================

微服务权限案例

一、数据表

在这里插入图片描述

二、父工程,管理依赖版本。子工程以及子工程中的模块
在这里插入图片描述

父工程——pom

<properties><!--确定各组件的依赖版本--><java.version>1.8</java.version><mybatis-plus.version>3.0.5</mybatis-plus.version>
</properties><dependencyManagement><dependencies><!--对上面的版本进行具体化的依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</verison></dependency></dependencies>
</dependencyManagement>

子工程common——pom

<dependencies><!--对父工程的依赖进行去版本处理--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided</scope></dependency>
</dependencies>

三、启动redis,启动Nacos注册中心
在这里插入图片描述
在这里插入图片描述

四、代码

密码处理

@Component
public class DefaultPasswordEncoder implements PasswordEncoder{public DefaultPasswordEncoder(){}public DefaultPasswordEncoder(int strength){}/**对密码进行MD5加密*/@Overridepublic String encode(CharSequence charSequence){return MD5.encrypt(charSequence.toString());}/**密码对比*/@Overridepublic boolean matches(CharSequence charSequence,String s){return encodedPassword.equals(MD5.encrypt(charSequence.toString()));}
}

token管理

@Component
public class TokenManager{//设置token有效时常private long takenEcpiration = 24*60*60*1000;//编码密钥(编码,解码)private String tokenSignKey = "123456";//使用jwt根据用户名生成tokenpublic String createToken(String username){String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration))//设置有效时长.signWith(SingatureAlgorithm.HS512,tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//根据token字符串得到用户信息public String getUserInfoFromToken(String token){String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userinfo;}//删除tokenpublic void removeToken(String token){}
}

退出登录

public class TokenLogoutHandler implements LogoutHandler{private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokeLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate){this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request,HttpServletResponse response,Authentication authentication){//从header里面获取tokenString token = request.getHeader("token");if(token != null){tokenManager.removeToken(token);//从token获取用户名String username = tokenManager.getUserInfoFromToken(token);redisTemplate.delete(username);}ResponseUtil.out(response,R.ok());}
}

未授权统一处理类

public class UnauthEntryPoint implements AuthenticationEntryPoint{@Overridepublic void commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e){ResponseUtil.out(httpServletResponse,R.error());}
}

认证和授权自定义过滤器

public class TokenLoginFilter extends UsernamePasswordAuthentiocationFilter{private TokenManager tokenManager;private RedisTemplate redisTemplate;private AuthenticationManager authenticationManager;//有参构造和无参构造(略)//构造函数中同时设定//获取表单提供的用户名和密码@Overridepublic Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException{try{User user = new ObjectMapper().readValue(request.getInputStream(),User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>())	);}catch(){}}//认证成功调用的方法@Overrideprotected void seccessFulAuthentication(HttpServletRequest requeest,HttpervletResponse response,FilterChain chain,Authentication authResult) throws IOException,ServletException{//认证成功,得到用户信息SecurityUser user = (SecurityUser)authResult.getPrincipal();//根据用户生成tokenString token = tokenManager.createToken(user.getCurrentUerInfo().username());}//认证失败调用的方法@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response,uthenticationException failed)throws IOException,ServletException{}
}

=============================================

工具类

public class AuthUtils{public static Authentication getPrincipal(){return SecurityContextHolder.getContext().getAuthentication();}
}
/**controller中进行应用
*/
@PostMapping("/app/user/coupons")
public Response<List<CouponResponse>> coupons(){LoginUser principal = (LoginUser) AuthUtils.getPrincipal().getPrincipal();return appDiscountCouponService.queryCoupons(principal.getId());
}

================================================================

SpringSecurity变成前后端分离,采用JWT做认证

什么是有状态:

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,不支持集群化部署

什么是无状态:

微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
  • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
  • 减小服务端存储压力

无状态登录的流程:

  • 首先客户端发送账户名、密码到服务端进行认证
  • 认证通过后,服务端将用户信息加密并且编码成一个 token,返回给客户端
  • 以后客户端每次发送请求,都需要携带认证的 token
  • 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息

JWT,全称是 Json Web Token

轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权:

常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjw
在这里插入图片描述

JWT包含三部分数据:

①、Header:头部,通常头部有两部分信息(对头部进行 Base64Url 编码(可解码),得到第一部分数据)

  • 声明类型,这里是JWT
  • 加密算法,自定义

②、Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息:(这部分也会采用 Base64Url 编码,得到第二部分数据)

  • iss (issuer):表示签发人
  • exp (expiration time):表示token过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

③、Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。

生成的数据格式如下图:(这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已)
在这里插入图片描述

JWT交互流程
在这里插入图片描述

  1. 应用程序或客户端向授权服务器请求授权
  2. 获取到授权后,授权服务器会向应用程序返回访问令牌
  3. 应用程序使用访问令牌来访问受保护资源(如 API)

因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了 RESTful 的无状态规范

存在的问题:

  • 续签问题,这是被很多人诟病的问题之一,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。
  • 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改 secret 来实现注销,服务端 secret 修改后,已经颁发的未过期的 token 就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。
  • 密码重置,密码重置后,原本的 token 依然可以访问系统,这时候也需要强制修改 secret。
  • 基于第 2 点和第 3 点,一般建议不同用户取不同 secret。

5个handler

  • 实现AuthenticationEntryPoint接口,当匿名请求需要登录的接口时拦截处理
  • AuthentiocationSuccessHandler接口,当登录成功后,该处理类的方法被调用
  • AuthenticationFailureHandler接口,当登录失败后,该处理类的方法被调用
  • AccessDeniedHandler接口,当登陆后,访问接口没有权限的时候该处理类的方法被调用
  • LogoutSuccessHandler接口,注销的时候调用

1个filter OncePerRequestFilter

前端发起请求的时候将token放在请求头中,在过滤器中对请求头进行解析

  • 如果有accessToken的请求头,取出token,解析成功,将解析出来的用户信息放到SpringSecurity的上下文中
  • 如果有accessToken的请求头,解析失败(无效token,或者过期失效)取不到用户信息,则放行
  • 没有accessToken的请求头,放行

AuthenticationEntryPoint

匿名未登录的时候访问,遇到需要登录认证的时候被调用

@Component
public class CustomerAuthenticationEntryPoint implements AuthentiocationEntryPoint{@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {//设置response状态码,返回错误信息等...ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));}
} 

AuthenticationSuccessHandler

输入用户名和密码认证成功后,调用的方法
获取用户信息,使用JWT生成token,然后返回token

@Slf4j
@Component
public class CustomerAuthentiocationSuccessHandler implements AuthenticationSuccessHandler{@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//获取当前用户,拿到用户名或者userId,创建tokenlog.info("登录成功……");CustomerUserDetails principal = (CustomerUserDetails)authentication.getPrincipal();//颁发tokenMap<String,Object> emptyMap = new HashMap<>(4);emptyMap.put(UserConstants.USER_ID,principal.getId());String token = JwtTokenUtil.generateToken(principal.getUsername(),emptyMap);ResponseUtil.out(ResultUtil.success(token));}
}

AuthenticationFailureHandler

登录失败调用该方法

@Slf4j
@Component
public class CustomerAuthenticationFailHandler implements AuthenticationFailureHandler{@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {//设置response状态码,返回错误信息等....ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.LOGIN_UNMATCH_ERROR));}
}

LogoutSuccessHandler

退出登录的时候调用
JWT无法主动控制失效,可以采用JWT+session方式,比如删除存在在Redis的token

@Component
public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {ResponseUtil.out(ResultUtil.success("Logout Success!"));}
}

AccessDeniedHandler

登录之后,访问缺失权限的资源会调用

@Component
@Slf4j
public class CustomerRestAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {ResponseUtil.out(403, ResultUtil.failure(ErrorCodeConstants.PERMISSION_DENY));}}

OncePerRequestFilter

过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中

@Component
@Slf4j
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter{@AutowiredCustomerUserDetailService customerUserDetailService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {String authHeader = request.getHeader(SecurityConstants.HEADER);if(authHeader != null && authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)){UserDetails userDetails = customerUserDetailService.loadUserByUsername(username);if (userDetails != null) {UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}}}}

配置WebSecurityConfigurerAdapter

将handler和filter注册到SpringSecurity中,同时配置一些放行的url

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)// 控制@Secured权限注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 这里需要交给spring注入,而不是直接new*/@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate CustomerUserDetailService customerUserDetailService;@Autowiredprivate CustomerAuthenticationFailHandler customerAuthenticationFailHandler;@Autowiredprivate CustomerAuthenticationSuccessHandler customerAuthenticationSuccessHandler;@Autowiredprivate CustomerJwtAuthenticationTokenFilter customerJwtAuthenticationTokenFilter;@Autowiredprivate CustomerRestAccessDeniedHandler customerRestAccessDeniedHandler;@Autowiredprivate CustomerLogoutSuccessHandler customerLogoutSuccessHandler;@Autowiredprivate CustomerAuthenticationEntryPoint customerAuthenticationEntryPoint;/*** 该方法定义认证用户信息获取的来源、密码校验的规则** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//auth.authenticationProvider(myauthenticationProvider)  自定义密码校验的规则//如果需要改变认证的用户信息来源,我们可以实现UserDetailsServiceauth.userDetailsService(customerUserDetailService).passwordEncoder(passwordEncoder);}@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** antMatchers: ant的通配符规则* ? 匹配任何单字符* * 匹配0或者任意数量的字符,不包含"/"* ** 匹配0或者更多的目录,包含"/"*/http.headers().frameOptions().disable();http//登录后,访问没有权限处理类.exceptionHandling().accessDeniedHandler(customerRestAccessDeniedHandler)//匿名访问,没有权限的处理类.authenticationEntryPoint(customerAuthenticationEntryPoint);//使用jwt的Authentication,来解析过来的请求是否有tokenhttp.addFilterBefore(customerJwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);http.authorizeRequests()//这里表示"/any"和"/ignore"不需要权限校验.antMatchers("/ignore/**", "/login", "/**/register/**").permitAll().anyRequest().authenticated()// 这里表示任何请求都需要校验认证(上面配置的放行).and()//配置登录,检测到用户未登录时跳转的url地址,登录放行.formLogin()//需要跟前端表单的action地址一致.loginProcessingUrl("/login").successHandler(customerAuthenticationSuccessHandler).failureHandler(customerAuthenticationFailHandler).permitAll()//配置取消session管理,又Jwt来获取用户状态,否则即使token无效,也会有session信息,依旧判断用户为登录状态.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//配置登出,登出放行.and().logout().logoutSuccessHandler(customerLogoutSuccessHandler).permitAll().and().csrf().disable();}}

实战

一、创建一个项目(添加 Spring Security 依赖,添加 jjwt 依赖)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

二、创建用户对象

public class User implements UserDetails {private String username;private String password;private List<GrantedAuthority> authorities;public String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}//省略getter/setter
}

三、Controller接口

设计是 /hello 接口可以被具有 user 角色的用户访问,
而 /admin 接口则可以被具有 admin 角色的用户访问

@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "hello jwt !";}@GetMapping("/admin")public String admin() {return "hello admin !";}
}

四、JWT过滤器

  • 一个是用户登录的过滤器,在用户的登录的过滤器中校验用户是否登录成功,如果登录成功,则生成一个token返回给客户端,登录失败则给前端一个登录失败的提示。
  • 第二个过滤器则是当其他请求发送来,校验token的过滤器,如果校验成功,就让请求继续执行。
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter{protected JwtLoginFilter(String defaultFilterProcessesUrl,AuthenticationManager authenticationManager){super(new AntPathRequestMatcher(defaultFilterProcessesUrl));setAuthenticationManager(authenticationManager);}/**从登录参数中提取出用户名密码,然后调用 AuthenticationManager.authenticate() 方法去进行自动校验*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {User user = new ObjectMapper().readValue(req.getInputStream(), User.class);return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));}/**如果校验成功,就会来到 successfulAuthentication 回调中,在 successfulAuthentication 方法中,将用户角色遍历然后用一个 , 连接起来,然后再利用 Jwts 去生成 token,按照代码的顺序,生成过程一共配置了四个参数,分别是用户角色、主题、过期时间以及加密算法和密钥,然后将生成的 token 写出到客户端*/@Overrideprotected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp,FilterChain chain, Authentication authResult)throws IOException, ServletException {Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();StringBuffer as = new StringBuffer();for(GrantedAuthority authority : authorities){as.append(authority.getAuthority()).append(",");}//Jwts 去生成 tokenString jwt = Jwts.builder().claim("authorities",as)//配置用户角色.setSubject(authResult.getName()).setExpiration(new Date(System.currentTimeMillis()+ 10 * 60 * 1000)).signWith(SignatureAlgorithm.HS512,"sang@123").compact();//将生成的 token 写出到客户端resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(jwt));out.flush();out.close();}/**校验失败就会来到 unsuccessfulAuthentication 方法中,在这个方法中返回一个错误提示给客户端即可*/protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp, AuthenticationException failed) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write("登录失败!");out.flush();out.close();}
}
public class JwtFilter extends GenericFilterBean{@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {//首先从请求头中提取出 authorization 字段,这个字段对应的 value 就是用户的 tokenHttpServletRequest req = (HttpServletRequest) servletRequest;String jwtToken = req.getHeader("authorization");System.out.println(jwtToken);//将提取出来的 token 字符串转换为一个 Claims 对象Claims claims = Jwts.parser().setSigningKey("sang@123").parseClaimsJws(jwtToken.replace("Bearer","")).getBody();String username = claims.getSubject();//获取当前登录用户名List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));//获取用户角色//创建一个 UsernamePasswordAuthenticationToken 放到当前的 Context 中,然后执行过滤链使请求继续执行下去UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);SecurityContextHolder.getContext().setAuthentication(token);filterChain.doFilter(req,servletResponse);}
}

五、SpringSecurity配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{/**未对密码进行加密,因此配置了 NoOpPasswordEncoder 的实例*/@BeanPasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}/**未连接数据库,在内存中配置了两个用户,两个用户具备不同的角色。*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("admin").password("123").roles("admin").and().withUser("sang").password("456").roles("user");}/**配置路径规则时, /hello 接口必须要具备 user 角色才能访问, /admin 接口必须要具备 admin 角色才能访问,POST 请求并且是 /login 接口则可以直接通过,其他接口必须认证后才能访问。*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/hello").hasRole("user").antMatchers("/admin").hasRole("admin").antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().authenticated().and().addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class).addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class).csrf().disable();//最后配置上两个自定义的过滤器并且关闭掉 csrf 保护}
}

六、测试
在这里插入图片描述

登录成功后返回的字符串就是经过 base64url 转码的 token,一共有三部分,通过一个 . 隔开,我们可以对第一个 . 之前的字符串进行解码,即 Header,如下:
在这里插入图片描述
再对两个 . 之间的字符解码,即 payload:
在这里插入图片描述
设置信息,由于 base64 并不是加密方案,只是一种编码方案,因此,不建议将敏感的用户信息放到 token 中
接下来再去访问 /hello 接口,注意认证方式选择 Bearer Token,Token 值为刚刚获取到的值,如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/154192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch 和 LangChain 合作开发可用于生产的 RAG 模板

作者&#xff1a;Aditya Tripathi 在过去的几个月里&#xff0c;我们一直与 LangChain 团队密切合作&#xff0c;他们在推出 LangServe 和 LangChain 模板方面取得了进展&#xff01; LangChain Templates 是一组用于构建生产质量的生成式 AI 应用程序的参考架构。 你可以在此处…

运动装备经营小程序商城效果如何

运动装备可包含服装、帐篷、渔具、箱包鞋帽等&#xff0c;对喜欢外出的人来说&#xff0c;靠谱的装备是关键&#xff0c;往往更容易选择品牌和信得过的商家。 而对商家来说&#xff0c;如何打造品牌提升卖货经营效率和提升营收是重中之重&#xff1b;互联网时代需要商家拓展线…

串口工作流程硬核解析,没有比这更简单的了!

串口通信,就是我们常说的串口通讯,是一种短距离、点对点的数据传输方式。它基于串行通信协议,通过串口线连接设备进行数据交互。串口在很多硬件系统中广泛使用,是工控机、单片机、外设设备之间信息交换的重要接口。 那串口是怎么工作的呢?我们举个形象的例子。假设A和B是两台…

Wireshark的数据包它来啦!

通过Wireshark工具&#xff0c;可以轻松的看到网卡的数据信息。通过Wireshark显示的数据包内容信息&#xff0c;通常分七栏&#xff0c;介绍一下&#xff1a; 1No.&#xff1a; 数据包编号。 2.Time Time显示时间&#xff0c;以1号数据包发生开始计时。 3.Source Source显示内容…

探秘TikTok社群:短视频中的共同体验

社交媒体平台TikTok成为全球用户分享创意、表达自我、建立连接的重要场所。在这个数字化的时代&#xff0c;TikTok社群不仅是个人创作者的聚集地&#xff0c;更是成千上万用户共同参与、体验的独特社交现象。 本文将深入探讨TikTok社群的形成、特点以及其中的共同体验&#xf…

[java进阶]——泛型类、泛型方法、泛型接口、泛型的通配符

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 泛型的基础知识&#xff1a; ♥A 泛型的好处&#xff1a; ♠A 泛型擦除&#xff1a; ♣A 泛型的小细节&#xff1a; 泛型的使用&#xff1a; ①泛型类&#xff1a; ②⭐泛型接口&#xff1a; ③泛型方法&…

大结局!OpenAI创始人奥特曼和 Greg Brockman 将加入微软!!!

持续48小时的OpenAI政变大戏终于迎来了大结局&#xff01; 微软堪称最大赢家&#x1f4a5;&#x1f4a5;&#x1f4a5; 微软CEO刚刚宣布&#xff1a; 我们仍然致力于与 OpenAI 的合作伙伴关系&#xff0c;并对我们的产品路线图、我们在 Microsoft Ignite 上宣布的一切继续创…

【基于Ubuntu下Yolov5的目标识别】保姆级教程 | 虚拟机安装 - Ubuntu安装 - 环境配置(Anaconda/Pytorch/Vscode/Yolov5) |全过程图文by.Akaxi

目录 一.【YOLOV5算法原理】 1.输入端 2.Backbone 3.Neck 4.输出端 二&#xff0e;【系统环境】 1.虚拟机的安装与创建 2.安装Ubuntu操作系统 3.环境的配置 3.1.Ubuntu下Anacoda安装以及虚拟环境配置 3.2.Pytorch安装 3.3.Vscode安装 3.4.Yolov5源码及环境获取安装…

SPI 实验

SPI介绍 SPI 是英语 Serial Peripheral interface 缩写&#xff0c;顾名思义就是串行外围设备接口。SPI 通信协 议是 Motorola 公司首先在其 MC68HCXX 系列处理器上定义的。SPI 接口是一种高速的全双工 同步的通信总线&#xff0c;已经广泛应用在众多 MCU、存储芯片、AD 转换器…

Java八股文(急速版)

Redis八股文 我看你在做项目的时候都使用到redis&#xff0c;你在最近的项目中哪些场景下使用redis呢? 缓存和分布式锁都有使用到。 问&#xff1a;说说在缓存方面使用 1.在我最写的物流项目中就使用redis作为缓存&#xff0c;当然在业务中还是比较复杂的。 2.在物流信息…

国外客户要求免费样品?我来教你如何应对

这一次的问题对外贸业务员来说是非常重要。无论你是做什么行业&#xff0c;无论你是做什么产品我相信这个问题对你来说超级有用。 关于发样品给客户我有四个方案来跟大家分享&#xff0c;我希望你能够喜欢希望你很认真的思考一下&#xff1a; 方法一【样品费及运费一起收】&am…

模方4.1.0新版本正式上线啦!

新增单体化自动建模&#xff0c;直角搭桥、复制三角形两种方式补洞等功能&#xff0c;还有更多功能优化&#xff0c;让你的三维模型更好看&#xff01; 欢迎前往官网下载试用→武汉大势智慧-实景三维-云端建模-新型基础设施

效率提升利器:Automa插件的实用指南

Automa是一个chrome扩展&#xff0c;通过拖拽0代码实现工作流&#xff0c;模拟网页的各种点击、表单填写等操作&#xff0c;使用时点击插件脚本一键执行&#xff0c;或者设置定时执行&#xff0c;从而简化我们的工作。 功能介绍 官方文档地址&#xff1a;Getting started | Au…

Spring的后处理器

目录 引言 BeanFactoryPostProcessor 注意 BeanPostProcessor 引言 Spring的后处理器是spring对外开发的重要扩展点&#xff0c;允许我们介入到Bean的整个实例化流程来&#xff0c;以达到动态注册BeanDefintion&#xff0c;动态修改BeanDefintion&#xff0c;以及动态修改Be…

怎么实现在微信公众号秒杀商品的功能呢

实现微信公众号秒杀商品的功能&#xff0c;需要结合微信公众平台和后端开发技术。下面将介绍整个实现过程&#xff0c;包括前期准备、开发流程和后期运营等方面。 一、前期准备 确定秒杀商品&#xff1a;选择适合秒杀的商品&#xff0c;要求数量充足、质量良好&#xff0c;同时…

一次性能测试,为啥把我逼疯了?

最近&#xff0c;公司领导让我做下性能方面的竞品对比&#xff0c;作为一个性能测试小白的我&#xff0c;突然接到这样的任务&#xff0c;下意识发出大大的疑问。 整理好心情&#xff0c;内心想着“领导一定是为了考验我&#xff0c;才给我这个任务的”&#xff0c;开始了这一…

electron使用electron-builder macOS windows 打包 签名 更新 上架

项目文件大概目录 1. 安装electron-builder 2. macOS 2.1 创建 Certificates, Identifiers & Profiles Devices 在mac上安装xcode&#xff0c;打开xcode&#xff0c;然后按以下步骤操作。 (1) xcode的菜单栏&#xff1a;xcode > settings。登录apple id。 (2) 登录后…

【操作系统】文件系统之文件共享与文件保护

文章目录 文件共享硬链接软链接 文件保护口令保护加密保护访问控制 文件共享 为了实现文件的共享&#xff0c;引入了“计数器”字段&#xff0c;当一个文件每被一个用户所共享&#xff0c;那么计数器就加一。如果一个用户删除文件&#xff0c;计数器相应的减一。如果计数器为0…

uniapp+vue3使用pinia,安卓端报错白屏

报错内容&#xff1a; reportJSException >>>> exception function:createInstanceContext, exception:white screen cause create instanceContext failed,check js stack ->at useStore2 (app-service.js:1487:15)at (app-service.js:1714:17)at (app-serv…

认识.NET Aspire:高效构建云原生应用的利器

简介 在几天前的.NET 8发布会上&#xff0c;来自微软的Glenn Condron和David Fowler为我们演示了.NET Aspire&#xff0c;在Visual Studio的帮助下&#xff0c;它展现出了惊人的开发效率。 短短的十分钟内&#xff0c;David现场演示了如何轻松创建了一个具有服务发现&#xf…