SpringBoot+SpringSecurity處理Ajax登入請求

來源:互聯網
上載者:User

標籤:pos   auto   app   use   調整   str   framework   避免   dmi   

最近在項目中遇到了這樣一個問題:前後端分離,前端用Vue來做,所有的資料請求都使用vue-resource,沒有使用表單,因此資料互動都是使用JSON,後台使用Spring Boot,許可權驗證使用了Spring Security,因為之前用Spring Security都是處理頁面的,這次單純處理Ajax請求,因此記錄下遇到的一些問題。這裡的解決方案不僅適用於Ajax請求,也可以解決移動端請求驗證。

本文是V部落項目在完成過程中的問題記錄,完整項目請小夥伴們移步這裡:https://github.com/lenve/VBlog建立工程

首先我們需要建立一個Spring Boot工程,建立時需要引入Web、Spring Security、MySQL和MyBatis(資料庫架構其實隨意,我這裡使用MyBatis),建立好之後,依賴檔案如下:

<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訊息摘要,我在後文中將對密碼進行簡單的處理。

建立資料庫並配置

為了簡化邏輯,我這裡建立了三個表,分別是使用者表、角色表、使用者角色關聯表,如下:

接下來我們需要在application.properties中對自己的資料庫進行簡單的配置,這裡各位小夥伴視自己的具體情況而定。

spring.datasource.url=jdbc:mysql:///vueblogspring.datasource.username=rootspring.datasource.password=123
構造實體類

這裡主要是指構造使用者類,這裡的使用者類比較特殊,必須實現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省略...}

實現了UserDetails介面之後,該介面中有幾個方法需要我們實現,四個返回Boolean的方法都是見名知意,enabled表示檔期賬戶是否啟用,這個我資料庫中確實有該欄位,因此根據查詢結果返回,其他的為了簡單期間都直接返回true,getAuthorities方法返回目前使用者的角色資訊,使用者的角色其實就是roles中的資料,將roles中的資料轉換為List之後返回即可,這裡有一個要注意的地方,由於我在資料庫中儲存的角色名稱都是諸如‘超級管理員’、‘普通使用者’之類的,並不是以ROLE_這樣的字元開始的,因此需要在這裏手動加上ROLE_,切記

另外還有一個Role實體類,比較簡單,按照資料庫的欄位建立即可,這裡不再贅述。

建立UserService

這裡的UserService也比較特殊,需要實現UserDetailsService介面,如下:

@Servicepublic 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;    }}

實現了UserDetailsService介面之後,我們需要實現該介面中的loadUserByUsername方法,即根據使用者名稱查詢使用者。這裡注入了兩個MyBatis中的Mapper,UserMapper用來查詢使用者,RolesMapper用來查詢角色。在loadUserByUsername方法中,首先根據傳入的參數(參數就是使用者登入時輸入的使用者名稱)去查詢使用者,如果查到的使用者為null,可以直接拋一個UsernameNotFoundException異常,但是我為了處理方便,返回了一個沒有任何值的User對象,這樣在後面的密碼比對過程中一樣會發現登入失敗的(這裡大家根據自己的業務需求調整即可),如果查到的使用者不為null,此時我們根據查到的使用者id再去查詢該使用者的角色,並將查詢結果放入到user對象中,這個查詢結果將在user對象的getAuthorities方法中用上。

Security配置

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

@Configurationpublic 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.將剛剛建立好的UserService注入進來,一會我們要用。
3.configure(AuthenticationManagerBuilder auth)方法中用來配置我們的認證方式,在auth.userDetailsService()方法中傳入userService,這樣userService中的loadUserByUsername方法在使用者登入時將會被自動調用。後面的passwordEncoder是可選項,可寫可不寫,因為我是將使用者的純文字密碼產生了MD5訊息摘要後存入資料庫的,因此在登入時也需要對純文字密碼進行處理,所以就加上了passwordEncoder,加上passwordEncoder後,直接new一個PasswordEncoder匿名內部類即可,這裡有兩個方法要實現,看名字就知道方法的含義,第一個方法encode顯然是對明文進行加密,這裡我使用了MD5訊息摘要,具體的實現方法是由commons-codec依賴提供的;第二個方法matches是密碼的比對,兩個參數,第一個參數是純文字密碼,第二個是密文,這裡只需要對明文加密後和密文比較即可(小夥伴如果對此感興趣可以繼續考慮密碼加鹽)。
4.configure(HttpSecurity http)用來配置我們的認證規則等,authorizeRequests方法表示開啟了認證規則配置,antMatchers(“/admin/**”).hasRole(“超級管理員”)表示/admin/**的路徑需要有‘超級管理員’角色的使用者才能訪問,我在網上看到小夥伴對hasRole方法中要不要加ROLE_首碼有疑問,這裡是不要加的,如果用hasAuthority方法才需要加。anyRequest().authenticated()表示其他所有路徑都是需要認證/登入後才能訪問。接下來我們配置了登入頁面為login_page,登入處理路徑為/login,登入使用者名稱為username,密碼為password,並配置了這些路徑都可以直接存取,登出登陸也可以直接存取,最後關閉csrf。在successHandler中,使用response返回登入成功的json即可,切記不可以使用defaultSuccessUrl,defaultSuccessUrl是只登入成功後重新導向的頁面,failureHandler也是由於相同的原因不使用failureUrl。
5.configure(WebSecurity web)方法中我配置了一些過濾規則,不贅述。
6.另外,對於靜態檔案,如/images/**/css/**/js/**這些路徑,這裡預設都是不攔截的。

Controller

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

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

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

測試

最後小夥伴可以使用POSTMAN或者RESTClient等工具來測試登入和許可權問題,我就不示範了。

Ok,經過上文的介紹,想必小夥伴們對Spring Boot+Spring Security處理Ajax登入請求已經有所瞭解了,好了,本文就說到這裡,有問題歡迎留言討論。

本文是V部落項目在完成過程中的問題記錄,完整項目請小夥伴們移步這裡:https://github.com/lenve/VBlog

更多資料請關注公眾號:

SpringBoot+SpringSecurity處理Ajax登入請求

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.