1. 程式人生 > 實用技巧 >SpringSecurity基礎功能詳解

SpringSecurity基礎功能詳解

  本篇目錄:

  一、預設情況

  二、自定義使用者認證

  三、自定義使用者登入頁面

  四、自定義登入成功、失敗處理

  五、圖形驗證碼

  六、記住我功能

  七、Session管理

  八、退出操作

  首先說明本文所用的SpringSecurity版本是2.0.4.RELEASE。下面逐個功能介紹。

  一、預設情況

  1、構建與配置

  1)pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

  2)application.properties

無需配置

  3)UserController.java

@GetMapping("/user")
public List<User> query(){
    List<User> users = new ArrayList<User>();
    users.add(new User("1","張三","123456",new Date()));
    return users;
}

  2、啟動與測試

  1)啟動程式,控制檯打印出預設密碼:“Using generated security password: 15a189e8-accb-407a-ad81-2283c8b3bdbf”

  

  2)瀏覽器輸入:http://localhost:8080/user,跳轉到表單登入頁面

  

  3)輸入預設使用者名稱user與預設使用者密碼15a189e8-accb-407a-ad81-2283c8b3bdbf,訪問到資料

  

  二、 自定義使用者認證

  1、實現UserDetailsService介面

@Component
public class MyUserDetailsService implements UserDetailsService{

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

  2、說明

  1)認證時使用者名稱任意,密碼是12345,輸錯密碼,提示壞的憑證。

  2)必須加密,實際專案中,將密碼passwordEncoder.encode("123456")進行加密,寫入資料庫。

  3)建構函式四個true假如為false依次代表:使用者已失效;使用者帳號已過期;使用者憑證已過期;使用者帳號已被鎖定。

return new User(username, passwordEncoder.encode("123456"),true,true,true,true, 
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

  三、自定義使用者登入頁面

  登入頁面/static/login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入</title>
</head>
<body>
<h2>標準登入頁面</h2>
<h3>表單登入</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr>
        <tr><td>密碼</td><td><input type="password" name="password"></td></tr>
        <tr><td colspan="2"><button type="submit">登入</button></td></tr>
    </table>
</form>
</body>
</html>

  1、loginPage指定登入頁面

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/login.html")//指定登入頁面
            .loginProcessingUrl("/authentication/form");//指定登入頁面中表單的url
        http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//該路徑不需要身份認證
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
}

  2、loginPage指定Controller,自定義判斷

  1)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/authentication/require")//指定需要認證時路徑
            .loginProcessingUrl("/authentication/form");//指定登入頁面中表單的url
        http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//該路徑不需要身份認證
            .antMatchers("/authentication/require").permitAll()
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
}

  2)SecurityController.java

@RestController
public class SecurityController {

    private RequestCache requestCache=new HttpSessionRequestCache();
    private RedirectStrategy redirectStrategy=new DefaultRedirectStrategy();
    
    @RequestMapping("/authentication/require")
    @ResponseStatus(code=HttpStatus.UNAUTHORIZED)
    public SimpleResponse requireAuthentication(HttpServletRequest request,HttpServletResponse response) throws Exception {
        SavedRequest savedRequest=requestCache.getRequest(request, response);
        if(savedRequest!=null) {
            String targetUrl=savedRequest.getRedirectUrl();
            System.out.println("引發跳轉的請求是:"+targetUrl);
            if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response,"/login.html");
            }
        }
        return new SimpleResponse("訪問的服務需要身份認證,請引導使用者到登入頁");
    }
}

  3)index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h2>index測試頁面</h2>
</body>
</html>

  測試輸入http://localhost:8080/index.html,跳轉到登入頁,輸入使用者名稱、密碼跳轉到index.html頁面。

  測試輸入http://localhost:8080/user,頁面打印出{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}。

  四、自定義登入成功、失敗處理

  1、構建與配置

  1)pom.xml新增處理json依賴

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>

  2)AuthenticationSuccessHandler.java

@Component("authenticationSuccessHandler")
public class AuthenticationSuccessHandler extends  SavedRequestAwareAuthenticationSuccessHandler{
    
    @Autowired 
    private SecurityProperties securityProperties;
    private ObjectMapper objectMapper=new ObjectMapper();
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        System.out.println("登入成功!");
        if(LoginResponseType.JSON.equals(securityProperties.getLoginType())) {//如果配置了JSON格式,返回如下資訊
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {//否則執行預設的方式,跳轉原請求地址
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

  3)AuthenticationFailureHandler.java

@Component("authenticationFailureHandler")
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    
    @Autowired
    private SecurityProperties securityProperties;
    private ObjectMapper objectMapper=new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        System.out.println("登入失敗!");
        if(LoginResponseType.JSON.equals(securityProperties.getLoginType())) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage())));
        }else {//執行預設的方式,跳轉loginPage配置的地址
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}

  4)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
        
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
            .loginPage("/authentication/require")//指定需要認證時路徑
            .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url
            .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯
            .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯
        http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//該路徑不需要身份認證
            .antMatchers("/authentication/require").permitAll()
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
}

  5)application.properties

project.security.loginType=REDIRECT

  6)SecurityCoreConfig.java

@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {

}

  7)LoginResponseType.java

public enum LoginResponseType {
    REDIRECT,
    JSON
}

  8)SecurityProperties.java

@ConfigurationProperties(prefix="project.security")
public class SecurityProperties {
    
    private LoginResponseType loginType=LoginResponseType.JSON;//預設JSON
    
    public LoginResponseType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginResponseType loginType) {
        this.loginType = loginType;
    }
}

  2、測試

  1)application.properties中配置REDIRECT,輸入localhost:8080/index.html,控制檯列印如下資訊,並跳轉登入頁

  

  1.1)輸入錯誤密碼,控制檯列印如下資訊,瀏覽器顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}

  

  1.2)輸入正確密碼,控制檯列印“登入成功!”,瀏覽器跳轉index.html,顯示:index測試頁面

  2)application.properties中配置JSON,輸入localhost:8080/user,控制檯列印如下資訊,

  

  瀏覽器顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}

  2.1)瀏覽器輸入:localhost:8080/index.html,控制檯列印如下資訊,並跳轉登入頁。

  

  2.3)輸入錯誤密碼,控制檯列印“登入失敗!”,瀏覽器顯示:{"content":"壞的憑證"} 

  2.4)輸入正確密碼,控制檯列印“登入成功!”,瀏覽器顯示登入資訊:

  

  問題:為什麼會列印兩次:“引發跳轉的請求”?

  五、圖形驗證碼

  1、構建與配置

  1)ImageCode.java

public class ImageCode{

    private BufferedImage image;
    private String code;
    private LocalDateTime expireTime;
    
    public BufferedImage getImage() {
        return image;
    }
    public void setImage(BufferedImage image) {
        this.image = image;
    }
    public ImageCode(BufferedImage image, String code, int expireIn) {
        this.image = image;
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public LocalDateTime getExpireTime() {
        return expireTime;
    }
    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }
    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}

  2)ValidateCodeController.java

@RestController
public class ValidateCodeController {
    
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request,HttpServletResponse response) throws Exception {    
        ImageCode imageCode = createImageCode(request);
        request.getSession().setAttribute("imageCodeSession", imageCode);
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
    }
    private ImageCode createImageCode(HttpServletRequest request) {
        int width=67;
        int height=23;
        BufferedImage image=new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
        Graphics g=image.getGraphics();
        Random random=new Random();
        g.setColor(getRandColor(200,250));
        g.fillRect(0,0, width, height);
        g.setFont(new Font("TIME NEW ROMAN", Font.ITALIC, 20));
        g.setColor(getRandColor(160,200));
        for(int i=0;i<155;i++) {
            int x=random.nextInt(width);
            int y=random.nextInt(height);
            int xl=random.nextInt(12);
            int yl=random.nextInt(12);
            g.drawLine(x, y, x+xl,y+yl);
        }
        String sRand="";
        for(int i=0;i<4;i++) {
            String rand=String.valueOf(random.nextInt(10));
            sRand+=rand;
            g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110), 20+random.nextInt(110)));
            g.drawString(rand,13*i+6,16);
        }
        g.dispose();
        return new ImageCode(image,sRand,60);
    }
    private Color getRandColor(int fc, int bc) {
        Random random=new Random();
        if(fc>255) {
            fc=255;
        }
        if(bc>255) {
            bc=255;
        }
        int r=fc+random.nextInt(bc-fc);
        int g=fc+random.nextInt(bc-fc);
        int b=fc+random.nextInt(bc-fc);
        return new Color(r,g,b);
    }
}

  3)ValidateCodeException.java

public class ValidateCodeException extends AuthenticationException {
    
    private static final long serialVersionUID = 1L;
    
    public ValidateCodeException(String msg) {
        super(msg);
    }
}

  4)ValidateCodeFilter.java

@Component
public class ValidateCodeFilter extends OncePerRequestFilter{

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if(StringUtils.equals("http://localhost:8080/authentication/form",request.getRequestURL()+"") &&
                StringUtils.equalsIgnoreCase(request.getMethod(),"post")) {
            try {
                validate(request);
            } catch (ValidateCodeException e) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
    private void validate(HttpServletRequest request){
        ImageCode codeInSession = (ImageCode)request.getSession().getAttribute("imageCodeSession");
        String codeInRequest = request.getParameter("imageCode");
        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException("驗證碼的值不能為空");
        }
        if (codeInSession == null) {
            throw new ValidateCodeException("驗證碼不存在");
        }
        if (codeInSession.isExpried()) {
            request.getSession().removeAttribute("imageCodeSession");
            throw new ValidateCodeException("驗證碼已過期");
        }
        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException("驗證碼不匹配");
        }
        request.getSession().removeAttribute("imageCodeSession");
    }
}

  5)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig2 extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
        
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);//認證前新增驗證碼過濾器
        http.formLogin()
            .loginPage("/authentication/require")//指定需要認證時路徑
            .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url
            .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯
            .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯
        http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//該路徑不需要身份認證
            .antMatchers("/authentication/require").permitAll()
            .antMatchers("/code/image").permitAll()//圖片驗證碼
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
}

  6)static/login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入</title>
</head>
<body>
<h2>標準登入頁面</h2>
<h3>表單登入</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr>
        <tr><td>密碼</td><td><input type="password" name="password"></td></tr>
        <tr><td>圖片驗證碼</td><td><input type="text" name="imageCode"><img src="/code/image"/></td></tr>
        <tr><td colspan="2"><button type="submit">登入</button></td></tr>
    </table>
</form>
</body>
</html>

  2、說明

  1)驗證碼處理流程為:生成驗證碼->放在Session中->驗證->清空Session

  2)過濾器OncePerRequestFilter,每一次請求只進入一次該過濾器

  六、記住我功能

  1、構建與配置

  1)pom.xml新增以下依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

  2)application.properties

project.security.loginType=REDIRECT
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3309/springsecurity
spring.datasource.username=root
spring.datasource.password=123456

  3)login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入</title>
</head>
<body>
<h2>標準登入頁面</h2>
<h3>表單登入</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr><td>使用者名稱</td><td><input type="text" name="username"></td></tr>
        <tr><td>密碼</td><td><input type="password" name="password"></td></tr>
        <tr><td>圖片驗證碼</td><td><input type="text" name="imageCode"><img src="/code/image"/></td></tr>
        <tr><td colspan="2"><input name="remember-me" type="checkbox" value="true"/>記住我</td></tr>
        <tr><td colspan="2"><button type="submit">登入</button></td></tr>
    </table>
</form>
</body>
</html>

  4)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);//認證前新增驗證碼過濾器
        http.formLogin()
            .loginPage("/authentication/require")//指定需要認證時路徑
            .loginProcessingUrl("/authentication/form")//指定登入頁面中表單的url
            .successHandler(authenticationSuccessHandler)//認證成功後自定義處理邏輯
            .failureHandler(authenticationFailureHandler);//認證失敗後自定義處理邏輯
        http.rememberMe()//記住我
            .tokenRepository(persistentTokenRepository())
            .tokenValiditySeconds(60*60*1)//記住我1小時
            .userDetailsService(userDetailsService);
        http.authorizeRequests()
            .antMatchers("/login.html").permitAll()//該路徑不需要身份認證
            .antMatchers("/authentication/require").permitAll()
            .antMatchers("/code/image").permitAll()
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        tokenRepository.setCreateTableOnStartup(true);//第一次執行開啟,建立資料庫的表,以後不需要,註釋掉
        return tokenRepository;
    }
}

  2、測試

  1)啟動專案,資料庫SpringSecurity中預設建立persistent_logins表,結構如下:

  

  2)訪問http://localhost:8080/index.html,用賬號user登入,persistent_logins表中存了user賬號的資訊

  

  3)重啟專案,再次訪問http://localhost:8080/index.html,無需登入直接進入index.html頁面

  4)驗證記住我時間

  4.1)設定為1分鐘,清空表persistent_logins,啟動專案,瀏覽器輸入http://localhost:8080/index.html,勾選記住我,登入。停止專案。

  4.2)一分鐘後,啟動專案,輸入http://localhost:8080/index.html,發現需要登入,驗證生效。勾選記住我,登入。

  4.3)查詢persistent_logins,發現裡面有兩條user資訊,分別是兩次登入時儲存的,如下:

  

  七、Session管理

  1、設定超時時間  

  application.properties,新增以下配置,Session配置為1分鐘(SpringBoot中最小一分鐘)

server.servlet.session.timeout=1m

  2、設定超時後跳轉地址

  1)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.sessionManagement()
            .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
        ... ...    
    }
}

  2)SecurityController.java

@RestController
public class SecurityController {
    ... ...
    @GetMapping("/session/invalid")
    @ResponseStatus(code=HttpStatus.UNAUTHORIZED)
    public SimpleResponse sessionInvalid() {
        System.out.println("session失效");
        return new SimpleResponse("session失效");
    }
}

  測試:啟動專案,訪問localhost:8080/index.html,登陸,停止專案後再次啟動,重新整理該地址,瀏覽器出現“session失效”。

  3、設定單機登陸

  WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.sessionManagement()
            .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
            .maximumSessions(1);//最大session數量,1代表只能一個登入,後面的會把前面的踢掉
        ... ...
    }
}

  測試:Chrome瀏覽器訪問localhost:8080/index.html,用sl登陸;換360瀏覽器訪問該地址,再次用sl登陸,重新整理Chrome,如下:

  

  4、Session達到最大數後,阻止後面的登入

  WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    http.sessionManagement()
        .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
        .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉
        .maxSessionsPreventsLogin(true);//session數量達到了後,阻止後面的登入
    ... ...        
    }
}

  測試:用兩個瀏覽器先後登入,第二個登入後頁面顯示:{"content":"訪問的服務需要身份認證,請引導使用者到登入頁"}

  5、Session被踢掉後的處理

  1)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.sessionManagement()
            .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
            .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉
            //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入
            .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法
        ... ...
    }
}

  2)MyexpiredSessionStrategy.java

public class MyexpiredSessionStrategy implements SessionInformationExpiredStrategy{

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        event.getResponse().setContentType("application/json;charset=UTF-8");
        event.getResponse().getWriter().write("併發登入");
    }
}

  測試 :先用Chrome瀏覽器登入,在用360登入,然後重新整理Chrome,頁面出現“併發登入” 。

  說明:不能與maxSessionsPreventsLogin同時設定,否則不會生效,會執行阻止後面的登入的邏輯。

  八、退出操作

  1、預設退出操作

  1)執行退出操作做的事:使當前Session失效;清除與當前使用者相關的remember-me記錄;清除當前的SecurityContext;重定向到登入頁。

  2)新增退出的超級連結:<a href="/logout">退出</a>,點選就能退出。

  2、自定義退出連線

  1)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.logout()
            .logoutUrl("/signOut")//指定退出的連線,預設/logout
        ... ...
    }
}

  2)新增退出的超級連結:<a href="/signOut">退出</a>,點選就能退出。

  3、自定義退出後跳轉的url

  WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.sessionManagement()
            //.invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
            .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉
            //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入
            .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法
        http.logout()
            .logoutUrl("/signOut")//指定退出的連線,預設/logout
            .logoutSuccessUrl("/logout.html");//自動退出後跳轉的url,預設跳到登入的url上
        http.authorizeRequests()
            .antMatchers(
                    "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
                    ).permitAll()//該路徑不需要身份認證
            .anyRequest()
            .authenticated();
        http.csrf().disable();//先禁止掉跨站請求偽造防護功能
    }
}

  說明:必須去掉 invalidSessionUrl 配置項,否則點選退出後,會跳轉到 invalidSessionUrl指定的連線。

  4、自定義退出後跳轉處理

  1)WebSecurityConfig.java

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    ... ...
    @Autowired
    private MyLogoutSuccessHandler logoutSuccessHandler;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ... ...
        http.sessionManagement()
            .invalidSessionUrl("/session/invalid")//session超時後的跳轉地址,不會進入loginPage定義的地址中了
            .maximumSessions(1)//最大session數量,1代表只能一個登入,後面的會把前面的踢掉
            //.maxSessionsPreventsLogin(true)//session數量達到了後,阻止後面的登入
            .expiredSessionStrategy(new MyexpiredSessionStrategy());//踢掉先登入的session,先登入的再請求後端進入該類的方法
        http.logout()
            .logoutUrl("/signOut")//指定退出的連線,預設/logout
            //.logoutSuccessUrl("/logout.html")//自動退出後跳轉的url,預設跳到登入的url上
            .logoutSuccessHandler(logoutSuccessHandler)//退出成功後,自定義的操作,不能與logoutSuccessUrl同時存在
            .deleteCookies("JSESSIONID");
        ... ...
    }
}

  2)MyLogoutSuccessHandler.java

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    
    @Autowired 
    private SecurityProperties securityProperties;
    private ObjectMapper objectMapper=new ObjectMapper();

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        System.out.println("onLogoutSuccess:退出成功!");
        LoginResponseType loginType = securityProperties.getLoginType();
        if(LoginResponseType.JSON.equals(loginType)) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse("退出成功!")));
        }else {
            response.sendRedirect("/logout.html");
        }
    }
}

  project.security.loginType配置為REDIRECT,退出後跳轉到 logout.html頁面;配置為JSON,頁面顯示出:{"content":"退出成功!"}。

  logoutSuccessHandler與logoutSuccessUrl同時配置,logoutSuccessUrl會失效。

  優先順序:logoutSuccessHandler >invalidSessionUrl >logoutSuccessUrl