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

 

 

 

 

 

 

 

 

 

   

 

  

 

 

  

 

 

  

 

  

 

 

 

 

  

 

 

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM