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");
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
/**
自定义配置类,设置用户名、密码
该配置类设置使用哪个service实现类
*/
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
/**
自定义登录页面
*/
@Override
protected 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{
@Override
public 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{
@Autowired
private UsersMapper usersMapper;
@Override
public 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.8java.version>
<mybatis-plus.version>3.0.5mybatis-plus.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis-plus.version}verison>
dependency>
dependencies>
dependencyManagement>
子工程common——pom
<dependencies>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<scope>providedscope>
dependency>
dependencies>
四、代码
密码处理
@Component
public class DefaultPasswordEncoder implements PasswordEncoder{
public DefaultPasswordEncoder(){
}
public DefaultPasswordEncoder(int strength){
}
/**
对密码进行MD5加密
*/
@Override
public String encode(CharSequence charSequence){
return MD5.encrypt(charSequence.toString());
}
/**
密码对比
*/
@Override
public 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根据用户名生成token
public 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;
}
//删除token
public 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;
}
@Override
public void logout(HttpServletRequest request,HttpServletResponse response,Authentication authentication){
//从header里面获取token
String 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{
@Override
public 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;
//有参构造和无参构造(略)
//构造函数中同时设定
//获取表单提供的用户名和密码
@Override
public 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(){}
}
//认证成功调用的方法
@Override
protected void seccessFulAuthentication(HttpServletRequest requeest,HttpervletResponse response,FilterChain chain,Authentication authResult) throws IOException,ServletException{
//认证成功,得到用户信息
SecurityUser user = (SecurityUser)authResult.getPrincipal();
//根据用户生成token
String token = tokenManager.createToken(user.getCurrentUerInfo().username());
}
//认证失败调用的方法
@Override
protected 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());
}
================================================================
什么是有状态:
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:
什么是无状态:
微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:
无状态登录的流程:
JWT,全称是 Json Web Token
轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权:
常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjw
JWT包含三部分数据:
①、Header:头部,通常头部有两部分信息(对头部进行 Base64Url 编码(可解码),得到第一部分数据)
②、Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息:(这部分也会采用 Base64Url 编码,得到第二部分数据)
③、Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。
生成的数据格式如下图:(这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已)
因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了 RESTful 的无状态规范
存在的问题:
5个handler
1个filter OncePerRequestFilter
前端发起请求的时候将token放在请求头中,在过滤器中对请求头进行解析
匿名未登录的时候访问,遇到需要登录认证的时候被调用
@Component
public class CustomerAuthenticationEntryPoint implements AuthentiocationEntryPoint{
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//设置response状态码,返回错误信息等
...
ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));
}
}
输入用户名和密码认证成功后,调用的方法
获取用户信息,使用JWT生成token,然后返回token
@Slf4j
@Component
public class CustomerAuthentiocationSuccessHandler implements AuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取当前用户,拿到用户名或者userId,创建token
log.info("登录成功……");
CustomerUserDetails principal = (CustomerUserDetails)authentication.getPrincipal();
//颁发token
Map<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));
}
}
登录失败调用该方法
@Slf4j
@Component
public class CustomerAuthenticationFailHandler implements AuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//设置response状态码,返回错误信息等
....
ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.LOGIN_UNMATCH_ERROR));
}
}
退出登录的时候调用
JWT无法主动控制失效,可以采用JWT+session方式,比如删除存在在Redis的token
@Component
public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
ResponseUtil.out(ResultUtil.success("Logout Success!"));
}
}
登录之后,访问缺失权限的资源会调用
@Component
@Slf4j
public class CustomerRestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
ResponseUtil.out(403, ResultUtil.failure(ErrorCodeConstants.PERMISSION_DENY));
}
}
过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中
@Component
@Slf4j
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter{
@Autowired
CustomerUserDetailService customerUserDetailService;
@Override
protected 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);
}
}
}
}
将handler和filter注册到SpringSecurity中,同时配置一些放行的url
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)// 控制@Secured权限注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 这里需要交给spring注入,而不是直接new
*/
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomerUserDetailService customerUserDetailService;
@Autowired
private CustomerAuthenticationFailHandler customerAuthenticationFailHandler;
@Autowired
private CustomerAuthenticationSuccessHandler customerAuthenticationSuccessHandler;
@Autowired
private CustomerJwtAuthenticationTokenFilter customerJwtAuthenticationTokenFilter;
@Autowired
private CustomerRestAccessDeniedHandler customerRestAccessDeniedHandler;
@Autowired
private CustomerLogoutSuccessHandler customerLogoutSuccessHandler;
@Autowired
private CustomerAuthenticationEntryPoint customerAuthenticationEntryPoint;
/**
* 该方法定义认证用户信息获取的来源、密码校验的规则
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.authenticationProvider(myauthenticationProvider) 自定义密码校验的规则
//如果需要改变认证的用户信息来源,我们可以实现UserDetailsService
auth.userDetailsService(customerUserDetailService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* antMatchers: ant的通配符规则
* ? 匹配任何单字符
* * 匹配0或者任意数量的字符,不包含"/"
* ** 匹配0或者更多的目录,包含"/"
*/
http
.headers()
.frameOptions().disable();
http
//登录后,访问没有权限处理类
.exceptionHandling().accessDeniedHandler(customerRestAccessDeniedHandler)
//匿名访问,没有权限的处理类
.authenticationEntryPoint(customerAuthenticationEntryPoint)
;
//使用jwt的Authentication,来解析过来的请求是否有token
http
.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.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
二、创建用户对象
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public 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过滤器
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter{
protected JwtLoginFilter(String defaultFilterProcessesUrl,AuthenticationManager authenticationManager){
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(authenticationManager);
}
/**
从登录参数中提取出用户名密码,然后调用 AuthenticationManager.authenticate() 方法去进行自动校验
*/
@Override
public 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 写出到客户端
*/
@Override
protected 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 去生成 token
String 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{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
//首先从请求头中提取出 authorization 字段,这个字段对应的 value 就是用户的 token
HttpServletRequest 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 的实例
*/
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
未连接数据库,在内存中配置了两个用户,两个用户具备不同的角色。
*/
@Override
protected 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 接口则可以直接通过,其他接口必须认证后才能访问。
*/
@Override
protected 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 值为刚刚获取到的值,如下: