10. Spring security

依赖

<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-security</artifactid>
</dependency>

添加依赖后,项目中所有资源都会被保护起来。启动项目后访问任何一个接口都会跳转到登陆页面,这个页面是Spring Security提供的

 

 默认的用户名是user ,默认的登录密码则在每次启动项目时随机生成

 

 登录成功后,用户就可以访问想要的接口了。

如果使用postman则在这里输入密码。同时不要忘记即时清理右上角的cookies,可能会引起密码错误。

 

当登陆成功后session会保存用户的登陆状态,也就是说用户可以无限访问其他页面。如果需要退出登陆则需要访问 http://localhost:8080/logout 来退出登陆。

 

Basic Auth

 

 

 

 

 

 

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
            .anyRequest()
            .authenticated()
            .and()
       .httpBasic(); } }

 

访问任何一个接口都会弹出一个登陆窗口,而 login 界面已经不存在了。我们无法访问。所有没有定义过的接口都会是Whitelabel Error Page。

 

 

 basic auth是无法访问 /logout 来退出的,因为登陆的密码携带在request中。

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
       // 允许不登陆就可访问以下接口
       .antMatcher("/", "index", "/css/*", "/js/*")
       .permitAll()
       // antMatcher 还可以定义访问方法
       //
.antMatchers(HttpMethod.POST, "/hello").permitAll()
            .anyRequest()
            .authenticated()
            .and()
       .httpBasic();
    }
}

 

使用自定义用户

前面的例子中使用的一直都是一个自动生成的用户,那么我们该如何自定义符合我们需求的用户呢。我们可以在 WebSecurityConfigurerAdapter 重写 userDetailsService

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user1 = User.builder()
                .username("user1")
                .password("password")
                .roles("STUDENT") // ROLE_STUDENT
                .build();

        UserDetails user2 = User.builder()
                .username("user2")
                .password("password")
                .roles("TEACHER") // ROLE_STUDENT
                .build();

        return new InMemoryUserDetailsManager(user1, user2);
    }

    // 省略 protected void configure(HttpSecurity http) throws Exception

}

经过测试我们无法使用自定义的用户进行登陆,因为缺少了加密密码步骤。

新建一个类

@Configuration
public class PasswordConfig {
    @Bean
    PasswordEncoder passwordEncoder() {
     // 10 是指密码的strength,是一个optional parameter
     //
return new BCryptPasswordEncoder(10); } }

把加密方法引入security config

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  // 方法1
private final PasswordEncoder passwordEncoder; @Autowired public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder;   }

  // 方法2 @Bean
public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }   // 以上两种方法可以二选一 @Bean @Override public UserDetailsService userDetailsService() { UserDetails user1 = User.builder() .username("user1") .password(passwordEncoder.encode(("password")) // 对密码进行加密 .roles("STUDENT") // ROLE_STUDENT .build(); UserDetails user2 = User.builder() .username("user2") .password(passwordEncoder.encode(("password")) // 对密码进行加密 .roles("TEACHER") // ROLE_STUDENT .build(); return new InMemoryUserDetailsManager(user1, user2); } // 省略 protected void configure(HttpSecurity http) throws Exception }

 现在我们就可以成功登陆了!

 

Role and Permission -> ROLE BASED AUTHENTICATION

1. create ApplicationUserPermission

public enum ApplicationUserPermission {
    STUDENT_READ("student:read"),
    STUDENT_WRITE("student:write"),
    COURSE_READ("course:read"),
    COURSE_WRITE("course:write"),

    private final String permission;

    ApplicationUserPermission(String permission) {
        this.permission = permission;
    }

    public String getPermission() {
        return permission;
    }
}

 

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>

 

2. create ApplicationUserRole

public enum ApplicationUserRole {
    STUDENT(Sets.newHashSet()),
    ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, COURSE_WRITE));

    private final Set<ApplicationUserPermission> permissions;

    ApplicationUserRole(Set<ApplicationUserPermission> permissions) {
        this.permissions = permissions;
    }

    public Set<ApplicationUserPermission> getPermissions() {
        return permissions;
    }
}

 

3. use ApplicationUserRole in the userDetailsService@Configuration


@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user1 = User.builder()
                .username("user1")
                .password(passwordEncoder.encode(("password")) // 对密码进行加密
                .roles(ApplicationUserRole.STUDENT.name()) // ROLE_STUDENT
                .build();

        UserDetails user2 = User.builder()
                .username("user2")
                .password(passwordEncoder.encode(("password")) // 对密码进行加密
                .roles(ApplicationUserRole.ADMIN.name()) // ROLE_STUDENT
                .build();

        return new InMemoryUserDetailsManager(user1, user2);
    }
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
       // 允许不登陆就可访问以下接口
       .antMatcher("/", "index", "/css/*", "/js/*")
       .permitAll()
       // antMatcher 还可以定义访问方法
       //
.antMatchers(HttpMethod.POST, "/hello").permitAll()
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name()) 
       // 只有Role是Student的User才可以访问api开头的接口
            .anyRequest()
            .authenticated()
            .and()
       .httpBasic();
    }
} 

如果方法的用户的role不是STUDENT则会收到forbidden

 

 

 

Permission based authentication

1. 添加新Role到ApplicationUserRole

public enum ApplicationUserRole {
  // 使用构造方法创建的enum实力 STUDENT(Sets.newHashSet()), ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, COURSE_WRITE)); ADMINTRAINEE(Sets.newHashSet(COURSE_READ, STUDENT_READ));
private final Set<ApplicationUserPermission> permissions;   
  // 构造方法 ApplicationUserRole(Set
<ApplicationUserPermission> permissions) { this.permissions = permissions; } public Set<ApplicationUserPermission> getPermissions() { return permissions; }

  public Set<SimpleGrantedAuthority> getGrantedAuthorities() {
    Set<SimpleGrantedAuthority permissions=

      getPermissions().stream()
      .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
      .collect(Collectors.toSet());
    permissions.add(nnew SimpleGrantedAuthority("ROLE_" + this.name()));
    return permissions;

  } }

 

2. 添加新用户到UserDetailsService

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user1 = User.builder()
                .username("user1")
                .password(passwordEncoder.encode(("password")) // 对密码进行加密
                //.roles(ApplicationUserRole.STUDENT.name()) // ROLE_STUDENT
                .authorities(ApplicationUserRole.STUDENT.getGrantedAuthorities())
          .build(); UserDetails user2
= User.builder() .username("user2") .password(passwordEncoder.encode(("password")) // 对密码进行加密 // .roles(ApplicationUserRole.ADMIN.name()) // ROLE_STUDENT .authorities(ApplicationUserRole.ADMIN.getGrantedAuthorities())
          .build();      UserDetails user3
= User.builder() .username("user3") .password(passwordEncoder.encode(("password")) // 对密码进行加密 // .roles(ApplicationUserRole.ADMINTRAINEE.name()) // ROLE_STUDENT .authorities(ApplicationUserRole.ADMINtRAINEE.getGrantedAuthorities())
          .build();
return new InMemoryUserDetailsManager(user1, user2, user3); } }

3. 给configure 添加新的过滤条件

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        

       // antMatcher 还可以定义访问方法        
       //
.antMatchers(HttpMethod.POST, "/hello").permitAll()   
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口

       // 将会根据顺序过滤,一但不符合条件就会返回forbidden
       .antMatchers(HttpMethod.DELETE, "/management/api/**").hasAuthority(ApplicationUserPermission.COURSE_WRITE.getPsermission())
       .antMatchers(HttpMethod.POST, "/management/api/**").hasAuthority(ApplicationUserPermission.COURSE_WRITE.getPermission())
       .antMatchers(HttpMethod.PUT, "/management/api/**").hasAuthority(ApplicationUserPermission.COURSE_WRITE.getPermission())
       .antMatchers(HttpMethod.GET, "/management/api/**").hasAnyRole(ApplicationUserRole.ADMIN.name(), ApplicationUserRole.ADMINTRAINEE.name())
       .anyRequest()
       .authenticated()
       .and()        
       .httpBasic();
  }
}

我们创建的三个用户的authorities将会是这样:

 

@PreAuthorize at Controller

上面的 antMatchers 还有另外已经写法,那就是在Controller 使用PreAuthorize注解

@GetMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN, ROLE_ADMINTRAINEE')")
public List<Student> getAllStudents() {
    ...
}

@PostMapping
@PreAuthorize("hasAuthority('student:write')")
public void registerNewStudent() {
    ...
}

@DeleteMapping
@PreAuthorize("hasAuthority('student:write')")
public void deleteStudent() {
    ...
}

@GetMapping
@PreAuthorize("hasAuthority('student:write')")
public void updateStudent() {
    ...
}

以上代码的效果和antMatchers完全相同,二者选其一即可。

如果使用PreAuthorize的话需要在 ApplicationSecurityConfig 上添加注解@EnableGlobalMethodSecurity(prePostEnabled = true)

PreAuthorize还可以使用:hasRole('ROLE_'), hasAnyRole('ROLE_, ROLE_,...'), hasAuthority('permission'), hasAnyAuthority('permission, permission')

 

Form based authentication

 

 

 SessionID 是储存在memory database中的所以重新启动service后登陆状态就会消失。当然我们也可以把SessionID储存在Postgres或Redis中。

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin(); 
  } 
} 

 

运行项目时会跳转到登陆页面,而不是像basic auth的登陆弹窗。登陆状态会一直保存直到 /logout

 

 

自定义登陆页面

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin()
            .loginPage("/login").permitAll; // 需要有resources/template/login.html文件
  } 
} 

 

书写html文件我们需要添加依赖

 

 

Controller

 

 

登陆后跳转到指定页面

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin()
            .loginPage("/login").permitAll
            .defaultSuccessUrl("/courses", true); // 需要有resources/template/courses.html文件
  } } 

 

Controller

@Controller
@RequestMapping("/")
public class TemplateController {

    @GetMapping("login")
    public String getLogin() {
        return "login"; //必须和HTML文件名相符
    }
    
    @GetMapping("courses")
    public String getLogin() {
        return "courses"; // 必须和HTML文件名相符
 } }

 

Remember me

 

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin()
            .loginPage("/login").permitAll
            .defaultSuccessUrl("/courses", true)
            .and()
            .rememberMe(); // defaults to 2 weeks
       // .rememberMe().tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21)).key("somethingverysecured");
  } }

在login界面添加一个checkbox,不需要任何其他代码只需选中check box那么remember-me就会是on.

 

 在网页的network查看:

 

 remember me是on时,在cookies中会多一个remember me cookie。remember me cookie 含有 username,expiration time,md5 hash of the above 2 value。

 

Logout

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin()
            .loginPage("/login").permitAll
            .defaultSuccessUrl("/courses", true)
            .and()
            .rememberMe().tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21)).key("somethingverysecured")
            .and()
            .logout()
            .logoutUrl("/logout")
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID", "remember-me" )
            .logoutSuccessUrl("/login");
  }
 } 

 

 

 

 

 

 

logout button

 

 

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override 
  protected void configure(HttpSecurity http) throws Exception { 
        http
            .csrf().disable() 
            .authorizeHttpRequests()        
            // 允许不登陆就可访问以下接口                            
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
     
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       // 只有Role是Student的User才可以访问api开头的接口  

       .anyRequest() 
       .authenticated() 
       .and()        
       .formLogin()
            .loginPage("/login").permitAll
            .defaultSuccessUrl("/courses", true)
            .passwordParameter("password") // 这几个parameter要和form中每个对应组件的name相同
            .usernameParameter("username")
            .and()
            .rememberMe().tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21)).key("somethingverysecured")
            .rememberMeParameter("remember-me")
            .and()
            .logout()
            .logoutUrl("/logout")
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID", "remember-me" )
            .logoutSuccessUrl("/login");
  }
 } 

 

 

Database Authentication

在UserDetailsService接口中看它的继承类

 

 

 Database Authentication需要使用的就是JdbcDaoImpl

 

 

 

创建ApplicationUser

public class ApplicationUser implements UserDetails {

    private final List<? extends GrantedAuthority> grantedAuthorities;
    private final String password;
    private final String username;
    private final boolean isAccountNonExpired;
    private final boolean isAccountLocked;
    private final boolean isCredentialsNonExpired;
    private final boolean isEnabled;

    public ApplicationUser(List<? extends GrantedAuthority> grantedAuthorities,
                           String password,
                           String username,
                           boolean isAccountNonExpired,
                           boolean isAccountLocked,
                           boolean isCredentialsNonExpired,
                           boolean isEnabled) {
        this.grantedAuthorities = grantedAuthorities;
        this.password = password;
        this.username = username;
        this.isAccountNonExpired = isAccountNonExpired;
        this.isAccountLocked = isAccountLocked;
        this.isCredentialsNonExpired = isCredentialsNonExpired;
        this.isEnabled = isEnabled;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return isAccountLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }
}

 

创建ApplicationUserService

@Service
public class ApplicationUserService implements UserDetailsService {
    private final ApplicationUSerDao applicationUSerDao;

    @Autowired
  // Qualifier("fake") 指定ApplicationUserDao使用的implement,如果只有一个可以不添加。这里指的就是@Reposity("fake")的FakeApplicationUserDaoService
public ApplicationUserService(@Qualifier("fake") ApplicationUserDao applicationUserDao) { this.applicationUSerDao = applicationUSerDao; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return applicationUSerDao .selectApplicationUserByUsername(username) .orElseThrow(() -> new UsernameNotFoundException(String.format("Username %s not found", username))); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }

 

创建ApplicationUserDao

public interface ApplicationUserDao {
    public Optional<ApplicationUser> selectApplicationUserByUsername(String username);
}

 

创建FakeApplicationUserDaoService用于代替数据库数据

@Repository("fake")
public class FakeApplicationUserDaoService implements ApplicationUSerDao{

    private final PasswordEncoder passwordEncoder;

    @Autowired
    public FakeApplicationUserDaoService(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public Optional<ApplicationUser> selectApplicationUserByUsername(String username) {
        return getApplicationUsers()
                .stream()
                .filter(applicationUser -> username.equals(applicationUser.getUsername()))
                .findFirst();
    }

    private List<ApplicationUser> getApplicationUsers() {
        List<ApplicationUser> applicationUsers = new ArrayList<>();
        applicationUsers.add(new ApplicationUser(
                "annasmith",
                passwordEncoder.encode("password"),
                STUDENT.getGrantedAuthorities(),
                true, true, true, true
                ));
        applicationUsers.add(new ApplicationUser(
                "linda",
                passwordEncoder.encode("password"),
                ADMIN.getGrantedAuthorities(),
                true, true, true, true
        ));
        applicationUsers.add(new ApplicationUser(
                "tom",
                passwordEncoder.encode("password"),
                ADMINTRAINEE.getGrantedAuthorities(),
                true, true, true, true
        ));
    }
}

 

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

   // 省略 protected void configure(HttpSecurity http) throws Exception

  private final PasswordEncoder passwordEncoder;
  private final ApplicationUserService applicationUserService;
  
  @Autowired
  public ApplicationSecurityConfig(PasswordEncoder passwordEncoder, ApplicationUserService applicationUserService) {
    this.passwordEncoder = passwordEncoder;
    this.applicationUserService = applicationUserService;
  }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(passwordEncoder); provider.setUserDetailsService(applicationUserService); return provider; } }

 

jwt(json web token)

 

 

 

1. 添加dependencies

<dependencies>

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
    </dependency>

    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
        <version>4.0.0</version>
    </dependency>

</dependencies>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

 

2. 当用户sends credentials时对用户名和密码进行检查

 

 

public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        try {
            UsernameAndPasswordAuthenticationRequest authenticationRequest =
                    new ObjectMapper().readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);

            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authenticationRequest.getUsername(),
                    authenticationRequest.getPassword());
            // 检查username和password是否正确
            return authenticationManager.authenticate(authentication);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

需要创建一个新类来匹配request的值

public class UsernameAndPasswordAuthenticationRequest {
    private String username;
    private String password;

    public UsernameAndPasswordAuthenticationRequest() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

3. 检查通过后返回token

public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Autowired
    private AuthenticationManager authenticationManager;

    // 省略 attemptAuthentication


    // 如果attemptAuthentication的验证不通过就不会运行这个方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        String key = "securesecuresecuresecuresecuresecuresecuresecuresecure";
        String token = Jwts.builder()
                .setSubject(authResult.getName()) // subject 就是要生成 token 的值,比如这里可以是用户名、用户 ID 等。
                .claim("authorities", authResult.getAuthorities())
                .setIssuedAt(new Date()) // JWT发行时的时间戳
                .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(2)))
                .signWith(Keys.hmacShaKeyFor(key.getBytes()))
          //我使用
.signWith(SignatureAlgorithm.HS256,secret.getBytes(StandardCharsets.UTF_8))
          .compact(); 
      response.addHeader(
"Authorization", "Bearer " + token);
  }
}

 

4. 在configure中添加 jwtFilter,jwt是一个stateless的

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            // authenticationManager() is a function of WebSecurityConfigurerAdapter
            .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager()))
            .authorizeHttpRequests()        
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       .anyRequest()
       .authenticated();
  }
}

 

5. create jwtTokenVerifier

 

 

public class JwtTokenVerifier extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader == null
                || !authorizationHeader.startsWith("Bearer ")) {
        // 去到下一个filter filterChain.doFilter(request, response);
return; } String token = authorizationHeader.replace("Bearer ", ""); try { String key = "securesecuresecuresecuresecuresecuresecuresecuresecure"; Jws<Claims> claimsJws = Jwts.parser() .setSigningKey(Keys.hmacShaKeyFor(key.getBytes())) .parseClaimsJws(token); Claims body = claimsJws.getBody(); String username = body.getSubject(); var authorities = (List<Map<String, String>>) body.get("authorities"); Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream() .map(m -> new SimpleGrantedAuthority(m.get("authority"))) .collect(Collectors.toSet()); Authentication authentication = new UsernamePasswordAuthenticationToken( username, null, simpleGrantedAuthorities ); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (JwtException e) { throw new IllegalStateException(String.format("Token %s cannot be truest", token)); }
      filterChain.doFilter(request, response); // 去下一个filter
} }

 

6. 添加这个filter

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            // authenticationManager() is a function of WebSecurityConfigurerAdapter
            .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager()))
            // this filter will be after the JwtUsernameAndPasswordAuthenticationFilter
            .addFilterAfter(new JwtTokenVerifier(), JwtUsernameAndPasswordAuthenticationFilter.class)
            .authorizeHttpRequests()        
       .antMatcher("/", "index", "/css/*", "/js/*").permitAll()        
       .antMatcher("/api/**").hasRole(ApplicationUserRole.STUDENT.name())        
       .anyRequest()
       .authenticated();
  }
}

 

JWT config

posted @ 2022-07-19 05:47  小白冲冲  阅读(135)  评论(0编辑  收藏  举报