溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot+SpringSecurity怎么處理Ajax登錄請求

發(fā)布時間:2021-12-07 14:23:30 來源:億速云 閱讀:139 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“SpringBoot+SpringSecurity怎么處理Ajax登錄請求”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“SpringBoot+SpringSecurity怎么處理Ajax登錄請求”吧!

問題:前后端分離,前端用Vue來做,所有的數(shù)據(jù)請求都使用vue-resource,沒有使用表單,因此數(shù)據(jù)交互都是使用JSON,后臺使用Spring Boot,權(quán)限驗證使用了Spring Security,因為之前用Spring Security都是處理頁面的,這次單純處理Ajax請求,因此記錄下遇到的一些問題。這里的解決方案不僅適用于Ajax請求,也可以解決移動端請求驗證。

創(chuàng)建工程

首先我們需要創(chuàng)建一個Spring Boot工程,創(chuàng)建時需要引入Web、Spring Security、MySQL和MyBatis(數(shù)據(jù)庫框架其實隨意,我這里使用MyBatis),創(chuàng)建好之后,依賴文件如下:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>

注意最后一個commons-codec依賴是我手動加入進來的,這是一個Apache的開源項目,可以用來生成MD5消息摘要,我在后文中將對密碼進行簡單的處理。

創(chuàng)建數(shù)據(jù)庫并配置

為了簡化邏輯,我這里創(chuàng)建了三個表,分別是用戶表、角色表、用戶角色關(guān)聯(lián)表,如下:

SpringBoot+SpringSecurity怎么處理Ajax登錄請求  

接下來我們需要在application.properties中對自己的數(shù)據(jù)庫進行簡單的配置,這里各位小伙伴視自己的具體情況而定。

spring.datasource.url=jdbc:mysql:///vueblog
spring.datasource.username=root
spring.datasource.password=123

構(gòu)造實體類

這里主要是指構(gòu)造用戶類,這里的用戶類比較特殊,必須實現(xiàn)UserDetails接口,如下:

public class User implements UserDetails {
    private Long id;
    private String username;
    private String password;
    private String nickname;
    private boolean enabled;
    private List<Role> roles;

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

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

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

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

    @Override
    public List<GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        }
        return authorities;
    }
    //getter/setter省略...
}

實現(xiàn)了UserDetails接口之后,該接口中有幾個方法需要我們實現(xiàn),四個返回Boolean的方法都是見名知意,enabled表示檔期賬戶是否啟用,這個我數(shù)據(jù)庫中確實有該字段,因此根據(jù)查詢結(jié)果返回,其他的為了簡單期間都直接返回true,getAuthorities方法返回當(dāng)前用戶的角色信息,用戶的角色其實就是roles中的數(shù)據(jù),將roles中的數(shù)據(jù)轉(zhuǎn)換為List之后返回即可,   這里有一個要注意的地方,由于我在數(shù)據(jù)庫中存儲的角色名都是諸如‘超級管理員’、‘普通用戶’之類的,并不是以ROLE_這樣的字符開始的,因此需要在這里手動加上ROLE_,切記

另外還有一個Role實體類,比較簡單,按照數(shù)據(jù)庫的字段創(chuàng)建即可,這里不再贅述。

創(chuàng)建UserService

這里的UserService也比較特殊,需要實現(xiàn)UserDetailsService接口,如下:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    RolesMapper rolesMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(s);
        if (user == null) {
            //避免返回null,這里返回一個不含有任何值的User對象,在后期的密碼比對過程中一樣會驗證失敗
            return new User();
        }
        //查詢用戶的角色信息,并返回存入user中
        List<Role> roles = rolesMapper.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

實現(xiàn)了UserDetailsService接口之后,我們需要實現(xiàn)該接口中的loadUserByUsername方法,即根據(jù)用戶名查詢用戶。這里注入了兩個MyBatis中的Mapper,UserMapper用來查詢用戶,RolesMapper用來查詢角色。在loadUserByUsername方法中,首先根據(jù)傳入的參數(shù)(參數(shù)就是用戶登錄時輸入的用戶名)去查詢用戶,如果查到的用戶為null,可以直接拋一個UsernameNotFoundException異常,但是我為了處理方便,返回了一個沒有任何值的User對象,這樣在后面的密碼比對過程中一樣會發(fā)現(xiàn)登錄失敗的(這里大家根據(jù)自己的業(yè)務(wù)需求調(diào)整即可),如果查到的用戶不為null,此時我們根據(jù)查到的用戶id再去查詢該用戶的角色,并將查詢結(jié)果放入到user對象中,這個查詢結(jié)果將在user對象的getAuthorities方法中用上。

Security配置

我們先來看一下我的Security配置,然后我再來一一解釋:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            /**
             * @param charSequence 明文
             * @param s 密文
             * @return
             */
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("超級管理員")
                .anyRequest().authenticated()//其他的路徑都是登錄后即可訪問
                .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                httpServletResponse.setContentType("application/json;charset=utf-8");
                PrintWriter out = httpServletResponse.getWriter();
                out.write("{\"status\":\"ok\",\"msg\":\"登錄成功\"}");
                out.flush();
                out.close();
            }
        })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write("{\"status\":\"error\",\"msg\":\"登錄失敗\"}");
                        out.flush();
                        out.close();
                    }
                }).loginProcessingUrl("/login")                .usernameParameter("username").passwordParameter("password").permitAll()                .and().logout().permitAll().and().csrf().disable();    }    @Override    public void configure(WebSecurity web) throws Exception {        web.ignoring().antMatchers("/reg");    } }

這是我們配置的核心,小伙伴們聽我一一道來:

1.首先這是一個配置類,因此記得加上@Configuration注解,又因為這是Spring Security的配置,因此記得繼承WebSecurityConfigurerAdapter。 

2.將剛剛創(chuàng)建好的UserService注入進來,一會我們要用。 

3.configure(AuthenticationManagerBuilder auth)方法中用來配置我們的認證方式,在auth.userDetailsService()方法中傳入userService,這樣userService中的loadUserByUsername方法在用戶登錄時將會被自動調(diào)用。后面的passwordEncoder是可選項,可寫可不寫,因為我是將用戶的明文密碼生成了MD5消息摘要后存入數(shù)據(jù)庫的,因此在登錄時也需要對明文密碼進行處理,所以就加上了passwordEncoder,加上passwordEncoder后,直接new一個PasswordEncoder匿名內(nèi)部類即可,這里有兩個方法要實現(xiàn),看名字就知道方法的含義,第一個方法encode顯然是對明文進行加密,這里我使用了MD5消息摘要,具體的實現(xiàn)方法是由commons-codec依賴提供的;第二個方法matches是密碼的比對,兩個參數(shù),第一個參數(shù)是明文密碼,第二個是密文,這里只需要對明文加密后和密文比較即可(小伙伴如果對此感興趣可以繼續(xù)考慮密碼加鹽)。  

4.configure(HttpSecurity http)用來配置我們的認證規(guī)則等,authorizeRequests方法表示開啟了認證規(guī)則配置,antMatchers("/admin/**").hasRole("超級管理員")表示/admin/**的路徑需要有‘超級管理員’角色的用戶才能訪問,我在網(wǎng)上看到小伙伴對hasRole方法中要不要加ROLE_前綴有疑問,這里是不要加的,如果用hasAuthority方法才需要加。anyRequest().authenticated()表示其他所有路徑都是需要認證/登錄后才能訪問。接下來我們配置了登錄頁面為login_page,登錄處理路徑為/login,登錄用戶名為username,密碼為password,并配置了這些路徑都可以直接訪問,注銷登陸也可以直接訪問,最后關(guān)閉csrf。在successHandler中,使用response返回登錄成功的json即可,切記不可以使用defaultSuccessUrl,defaultSuccessUrl是只登錄成功后重定向的頁面,使用failureHandler也是由于相同的原因。   

5.configure(WebSecurity web)方法中我配置了一些過濾規(guī)則,不贅述。 

6.另外,對于靜態(tài)文件,如/images/**、/css/**、/js/**這些路徑,這里默認都是不攔截的。  

Controller

最后來看看我們的Controller,如下:

@RestController
public class LoginRegController {

    /**
     * 如果自動跳轉(zhuǎn)到這個頁面,說明用戶未登錄,返回相應(yīng)的提示即可
     * <p>
     * 如果要支持表單登錄,可以在這個方法中判斷請求的類型,進而決定返回JSON還是HTML頁面
     *
     * @return
     */
    @RequestMapping("/login_page")
    public RespBean loginPage() {
        return new RespBean("error", "尚未登錄,請登錄!");
    }
}

這個Controller整體來說還是比較簡單的,RespBean一個響應(yīng)bean,返回一段簡單的json,不贅述,這里需要小伙伴注意的是login_page,我們配置的登錄頁面是一個login_page,但實際上login_page并不是一個頁面,而是返回一段JSON,這是因為當(dāng)我未登錄就去訪問其他頁面時Spring Security會自動跳轉(zhuǎn)到到login_page頁面,但是在Ajax請求中,不需要這種跳轉(zhuǎn),我要的只是是否登錄的提示,所以這里返回json即可。

感謝各位的閱讀,以上就是“SpringBoot+SpringSecurity怎么處理Ajax登錄請求”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對SpringBoot+SpringSecurity怎么處理Ajax登錄請求這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI