天天看點

如何在SpringBoot項目中引入SpringSecurity進行權限控制

1、前言

  為了友善進行項目開發,自己搭建了一套簡單的基于RBAC模型的權限管理系統,其中涉及到的使用者、角色、資源(菜單)等子產品已經實作了,現在需要引入SpringSecurity來實作認證和授權。

2、引入依賴

  這裡使用的SpringSecurity的版本是:5.0.8.RELEASE,SpringBoot版本是:2.0.5.RELEASE。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
           
3、建立UserDetailsService實作類

  UserDetailsService實作類,用于加載使用者資訊。主要實作了UserDetailsService 接口的loadUserByUsername()方法,根據使用者名從資料庫查詢使用者資訊和使用者權限資訊,最後封裝成了UserDetails 對象。

@Component("userDetailsService")
public class QriverUserDetailsService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(QriverUserDetailsService.class);

    @Autowired
    private UserService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleService roleService;

    /**
     * 根據username加載資料庫中的使用者,并建構UserDetails對象。
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        User param = new User(username);
        User user = userService.querySingle(param);
        if(user == null) {
            logger.info("使用者名不存在,使用者名:" + username);
            throw new UsernameNotFoundException("使用者名不存在");
        }
        UserRole userRole = new UserRole();
        userRole.setUserId((String) user.getId());
        List<UserRole> userRoles = userRoleService.queryList(userRole);
        for(UserRole uRole : userRoles) {//儲存Role的ID
            authorities.add(new SimpleGrantedAuthority(uRole.getRoleName()));
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),authorities);
    }
}
           
4、建立PasswordEncoder實作類

  PasswordEncoder實作類,從5.0版本開始強制要求設定,主要用來配置加密方式,這裡使用了明文的方式。

@Component("nonPasswordEncoder")
public class QriverNonPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        //加密方法可以根據自己的需要修改
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encode(rawPassword).equals(encodedPassword);
    }
}
           
5、配置SpringSecurity

  首先,定義SpringSecurityConfig 配置類,該類繼承了WebSecurityConfigurerAdapter 抽象類。

  然後,添加注解@Configuration、@EnableWebSecurity,分别表示配置類和開啟 Security 服務。

  然後,重寫configure(AuthenticationManagerBuilder auth)方法,該方法主要用來配置AuthenticationManager對象相關内容,這裡主要配置了自定義的UserDetailsService實作類和PasswordEncoder實作類。

  最後,通過重寫configure(HttpSecurity http)方法,進行配置HttpSecurity 相關内容,主要配置了那些URL需要鑒權那些不需要鑒權,自定義登入頁面、登入成功或失敗時跳轉路徑、異常攔截等内容。

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private QriverUserDetailsService userDetailsService;

    @Autowired
    private QriverNonPasswordEncoder nonPasswordEncoder;

    /**
     * 配置AuthenticationManager對象
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //校驗使用者
        auth.userDetailsService( userDetailsService )
                .passwordEncoder(nonPasswordEncoder);
    }

    /**
     * 配置 HttpSecurity
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //設定不需要鑒權的位址
                .antMatchers("/error","/","index","/login","/login-error","/401","/static/**").permitAll()
                //其他請求均需要鑒權
                .anyRequest().authenticated()
                .and()
                //登入成功跳轉頁面
                .formLogin().defaultSuccessUrl("/index/toIndex").
                //自定義登入界面
                loginPage( "/login" ).
                //登入失敗跳轉頁面
                failureUrl( "/login-error" )
                .and()
                .exceptionHandling().accessDeniedPage( "/401" )
                .and().csrf().disable()
                .headers().frameOptions().disable();
        http.logout().logoutSuccessUrl( "/" );
    }
}
           
關于"/login"、"/login-error"、"/index/toIndex"等api接口,這裡不再貼出代碼了。
6、登入頁面

  這裡就是一個普通的from表單,最重要的就是from表單的action方法,這裡使用了“/login”,且method=“post”,“/login”是SpringSecurity預設的位址,可以在HttpSecurity 中進行配置。

這裡需要注意的是,之前自己實作的登入驗證,需要首先定義一個“/doLogin”方法,然後登入界面通過from表單或AJAX進行請求,而在SpringSecurity中,則不需要再定義該方法,預設已經實作了,隻需要通過HttpSecurity的loginProcessingUrl()方法修改預設的請求的路徑位址即可。
//部分代碼

<body class="gray-bg">
<div class="middle-box text-center loginscreen  animated fadeInDown">
    <div>
        <div style="margin:25% 0 5% 0;">
            <img src="${ctxPath}/static/img/logo.png"  width="45%;">
        </div>
        <h3>歡迎使用 綜合管理平台</h3>

        <form class="m-t" role="form" method="post" action="${ctxPath}/login">
            <div class="form-group">
                <input type="text" id="username" name="username" class="form-control" placeholder="使用者名" value="admin" required="">
            </div>
            <div class="form-group">
                <input type="password" id="password" name="password" class="form-control" placeholder="密碼" value="123456" required="">
            </div>
            <button type="submit" class="btn btn-primary block full-width m-b">登 錄</button>
        </form>
    </div>
</div>
</body>
           
7、其他

  通過上述步驟,我們基本上已經實作了SpringSecurity的內建,這個時候我們的使用者鑒權就會通過SpringSecurity被保護起來了。後續,我們會繼續細化一些細節,比如使用者名密碼錯誤時,如何更加友好提示?如何登入後,還回到目前頁面等功能。

繼續閱讀