SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—五、整合SpringSecurity(下)

目錄

SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—一、項目簡介和開發(fā)環(huán)境準(zhǔn)備
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—二、日志、接口文檔等實現(xiàn)
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—三、主要頁面及接口實現(xiàn)
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—四、整合SpringSecurity(上)
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—五、整合SpringSecurity(下)
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—六、SpringSecurity整合jwt
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—七、處理一些問題
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—八、AOP記錄用戶、異常日志
SpringSecurity權(quán)限管理系統(tǒng)實戰(zhàn)—九、數(shù)據(jù)權(quán)限的配置

前言

上篇文章SpringSecurity整合了一半,這次把另一半整完,所以本篇的序號接著上一篇。

七、自定義用戶信息

前面我們登錄都是用的指定的用戶名和密碼或者是springsecurity默認(rèn)的用戶名和打印出來的密碼。我們要想連接上自定義數(shù)據(jù)庫只需要實現(xiàn)一個自定義的UserDetailsService。

我們新建一個JwtUserDto繼承UserDetails并實現(xiàn)它的方法

@Data
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
    //用戶數(shù)據(jù)
    private MyUser myUser;
    //用戶權(quán)限的集合
    @JsonIgnore
    private List<GrantedAuthority> authorities;
        
    public List<String> getRoles() {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    }
    //加密后的密碼
    @Override
    public String getPassword() {
        return myUser.getPassword();
    }
    //用戶名
    @Override
    public String getUsername() {
        return myUser.getUserName();
    }
    //是否過期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    //是否鎖定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //憑證是否過期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //是否可用
    @Override
    public boolean isEnabled() {
        return myUser.getStatus() == 1 ? true : false;
    }
}

自定義一個UserDetailsServiceImpl實現(xiàn)UserDetailsService

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Autowired
    private MenuDao menuDao;
    @Override
    public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
        MyUser user = userService.getUser(userName);//根據(jù)用戶名獲取用戶
        if (user == null ){
            throw new UsernameNotFoundException("用戶名不存在");//這個異常一定要拋
        }else if (user.getStatus().equals(MyUser.Status.LOCKED)) {
            throw new LockedException("用戶被鎖定,請聯(lián)系管理員");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        List<MenuIndexDto> list = menuDao.listByUserId(user.getId());
        List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
        for (String authority : collect){
            if (!("").equals(authority) & authority !=null){
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
                grantedAuthorities.add(grantedAuthority);
            }
        }//將用戶所擁有的權(quán)限加入GrantedAuthority集合中
        JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
        return loginUser;
    }

}

這里在獲取權(quán)限的時候遇到了個小小的坑,就是mybatis數(shù)據(jù)里的空值和null,在你從未對這個數(shù)據(jù)修改時,它就是null。如果修改了又刪除掉了,它就會是空值。

1.png

meudao中的listByUserId方法

 @Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type,sp.permission  " +
            "FROM my_role_user sru " +
            "INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " +
            "LEFT JOIN my_menu sp ON srp.menu_id = sp.id " +
            "WHERE " +
            "sru.user_id = #{userId}")
    @Result(property = "title",column = "name")
    @Result(property = "href",column = "url")
    List<MenuIndexDto> listByUserId(@Param("userId")Integer userId);

八、加密

老話題來聊一聊,加密的重要性。

2011年國內(nèi)某開發(fā)者社區(qū)(可不就是csdn嗎)被攻擊數(shù)據(jù)庫,600多萬明文存儲的用戶賬號被公開,大量用戶隱私泄露。

這是個老梗了,幾乎每篇說加密重要性的博文中,csdn的事就要被拿出來遛一遛。

那么為什么密碼加密怎么重要??因為在你的數(shù)據(jù)庫被攻擊泄露了數(shù)據(jù)時,如果你的密碼也被黑客掌握,那么即使你修復(fù)好了數(shù)據(jù)庫泄露的問題,黑客手上仍然還有著用戶的密碼(總不能要求所有用戶修改密碼吧)

所以我們需要在系統(tǒng)開發(fā)之初就盡量的避免這種問題。

那么說了這么多,怎么來加密呢?

其實在SpringSecurity種已經(jīng)內(nèi)置了密碼的加密機(jī)制,只需要實現(xiàn)一個PasswordEncoder接口即可。

來看一下源碼

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

  • encode():把參數(shù)按照特定的解析規(guī)則進(jìn)行解析。
  • matches()驗證從存儲中獲取的編碼密碼與編碼后提交的原始密碼是否匹配。如果密碼匹配,則返回 true;如果不匹配,則返回 false。
  • upgradeEncoding():如果解析的密碼能夠再次進(jìn)行解析且達(dá)到更安全的結(jié)果則返回 true,否則返回 false。默認(rèn)返回 false。

第一個參數(shù)表示需要被解析的密碼。第二個參數(shù)表示存儲的密碼。

Spring Security 還內(nèi)置了幾種常用的 PasswordEncoder 接口,官方推薦使用的是BCryptPasswordEncoder

。我們來配置一下。在SpringConfig種添加如下代碼。

    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }//自定義userDetailsService加密

是不是十分簡單,我們再重啟項目,這時候控制臺就不再打印密碼,現(xiàn)在需要輸入數(shù)據(jù)庫中的用戶名密碼才能登錄。

九、獲取用戶信息

之前我們在繪制菜單時,把用戶的id給寫死了?,F(xiàn)在我們要從SpringSecurity中來獲取用戶信息。

有兩種方法獲取已登錄用戶的信息,一種是從session中拿,另一種就是SpringSecurity提供的方法。這里選擇后一種方法。

我們可以通過以下方法來獲取登錄后用戶的信息(其余還有獲取登錄ip等方法,不多介紹)

SecurityContextHolder.getContext().getAuthentication().getPrincipal()

我們轉(zhuǎn)換下類型

JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();

打印一下jwtUserDto,看到我們確實拿到了用戶的信息

4.png

那么我們改寫下通過用戶id獲取菜單這個方法

    @GetMapping(value = "/index")
    @ResponseBody
    @ApiOperation(value = "通過用戶id獲取菜單")
    public List<MenuIndexDto> getMenu() {
        JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Integer userId = jwtUserDto.getMyUser().getId();
        return menuService.getMenu(userId);
    }

在將前端寫死的userId刪除?,F(xiàn)在我們已經(jīng)能根據(jù)登錄用戶的不同來自動繪制菜單了。

擁有admin權(quán)限的用戶

2.png

普通權(quán)限的用戶

[圖片上傳中...(4.png-e8bce9-1596545372506-0)]

十、授權(quán)

我們目前只是繪制出了不同權(quán)限用戶能操作的界面,但是還沒有真正的進(jìn)行權(quán)限控制。

之前在七中,我們已經(jīng)將每個用戶所擁有的權(quán)限集合放入了GrantedAuthority集合中

在之前打印的用戶信息中可以看到 authorities中就是該用戶所擁有的權(quán)限
[圖片上傳失敗...(image-9a5e59-1596545532496)]

SpringSecurity會自動幫我們進(jìn)行權(quán)限控制。而我們要做的就是在需要進(jìn)行權(quán)限控制的方法上添加上權(quán)限標(biāo)識即可。

例如:用戶管理的權(quán)限標(biāo)識是user:list

5.png

我們只需要在相關(guān)的接口上加上@PreAuthorize("hasAnyAuthority('user:list')")即可

    @GetMapping("/index")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public String index(){
        return "system/user/user";
    }
    @GetMapping
    @ResponseBody
    @ApiOperation(value = "用戶列表")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public Result<MyUser> userList(PageTableRequest pageTableRequest, UserQueryDto userQueryDto){
        pageTableRequest.countOffset();
        return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),userQueryDto);
    }

現(xiàn)在我們登錄普通用戶來操作相關(guān)接口,發(fā)現(xiàn)報錯

6.png

控制臺打印

7.png

修改所有接口,在需要權(quán)限控制的接口上添加注解

十一、自定義異常處理

雖說現(xiàn)在功能已經(jīng)實現(xiàn)了,用戶雖說不能訪問沒有權(quán)限的功能了,但是異常沒有處理。如果點擊,如果前端也沒有做錯誤的攔截的話,用戶會看到一串的報錯信息,這很不友好,并且也會對服務(wù)器造成壓力。

我們只需要在之前創(chuàng)建的全局異常處理類中捕獲上圖的異常即可。

    @ExceptionHandler(AccessDeniedException.class)
    public Result handleAuthorizationException(AccessDeniedException e)
    {
        log.error(e.getMessage());
        return Result.error().code(ResultCode.FORBIDDEN).message("沒有權(quán)限,請聯(lián)系管理員授權(quán)");
 }

重啟項目,在前端書寫相應(yīng)規(guī)則,就會十分友好

8.png

十二、自定義退出登錄

其實SpringSecurity默認(rèn)注冊了一個/logout路由,通過這個路由可以注銷登錄狀態(tài),包括Session和remember-me等等。

我們可以直接在SpringSecurityConfig的configure中定義相應(yīng)規(guī)則,類似formlogin。也可以自定義一個LogoutHadnler,具體可以看這篇文章

至此SpringSecurity的一些常用功能已經(jīng)實現(xiàn),下一節(jié)我們整合jwt實現(xiàn)無狀態(tài)登錄

本系列giteegithub中同步更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容