真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

SpringBootSecurity詳解

簡介

Spring Security,這是一種基于 Spring AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時(shí)在 Web 請(qǐng)求級(jí)和方法調(diào)用級(jí)處理身份確認(rèn)和授權(quán)。

成都創(chuàng)新互聯(lián)公司于2013年創(chuàng)立,先為玉泉等服務(wù)建站,玉泉等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為玉泉企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

工作流程

從網(wǎng)上找了一張Spring Security 的工作流程圖,如下。
Spring Boot Security 詳解
圖中標(biāo)記的MyXXX,就是我們項(xiàng)目中需要配置的。

快速上手

建表

表結(jié)構(gòu)
Spring Boot Security 詳解
建表語句

DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;

CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`) 
);

INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/common','common',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/user/admin','admin',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

pom.xml


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.boot
    spring-boot-starter-thymeleaf


    org.springframework.boot
    spring-boot-starter-web


    org.thymeleaf.extras
    thymeleaf-extras-security4
 

application.yml

spring:
  thymeleaf:
    mode: HTML5
    encoding: UTF-8
    cache: false

  datasource:
    driver-class-name: com.MySQL.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

User

public class User implements UserDetails , Serializable {

    private Long id;
    private String username;
    private String password;

    private List authorities;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public List getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List authorities) {
        this.authorities = authorities;
    }

    /**
     * 用戶賬號(hào)是否過期
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 用戶賬號(hào)是否被鎖定
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 用戶密碼是否過期
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 用戶是否可用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }

}

上面的 User 類實(shí)現(xiàn)了 UserDetails 接口,該接口是實(shí)現(xiàn)Spring Security 認(rèn)證信息的核心接口。其中 getUsername 方法為 UserDetails 接口 的方法,這個(gè)方法返回 username,也可以是其他的用戶信息,例如手機(jī)號(hào)、郵箱等。getAuthorities() 方法返回的是該用戶設(shè)置的權(quán)限信息,在本實(shí)例中,從數(shù)據(jù)庫取出用戶的所有角色信息,權(quán)限信息也可以是用戶的其他信息,不一定是角色信息。另外需要讀取密碼,最后幾個(gè)方法一般情況下都返回 true,也可以根據(jù)自己的需求進(jìn)行業(yè)務(wù)判斷。

Role

public class Role implements GrantedAuthority {

    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getAuthority() {
        return name;
    }

}

Role 類實(shí)現(xiàn)了 GrantedAuthority 接口,并重寫 getAuthority() 方法。權(quán)限點(diǎn)可以為任何字符串,不一定非要用角色名。

所有的Authentication實(shí)現(xiàn)類都保存了一個(gè)GrantedAuthority列表,其表示用戶所具有的權(quán)限。GrantedAuthority是通過AuthenticationManager設(shè)置到Authentication對(duì)象中的,然后AccessDecisionManager將從Authentication中獲取用戶所具有的GrantedAuthority來鑒定用戶是否具有訪問對(duì)應(yīng)資源的權(quán)限。

MyUserDetailsService

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //查數(shù)據(jù)庫
        User user = userMapper.loadUserByUsername( userName );
        if (null != user) {
            List roles = roleMapper.getRolesByUserId( user.getId() );
            user.setAuthorities( roles );
        }

        return user;
    }

}

Service 層需要實(shí)現(xiàn) UserDetailsService 接口,該接口是根據(jù)用戶名獲取該用戶的所有信息, 包括用戶信息和權(quán)限點(diǎn)。

MyInvocationSecurityMetadataSourceService

@Component
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private PermissionMapper permissionMapper;

    /**
     * 每一個(gè)資源所需要的角色 Collection決策器會(huì)用到
     */
    private static HashMap> map =null;

    /**
     * 返回請(qǐng)求的資源需要的角色
     */
    @Override
    public Collection getAttributes(Object o) throws IllegalArgumentException {
        if (null == map) {
            loadResourceDefine();
        }
        //object 中包含用戶請(qǐng)求的request 信息
        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
        for (Iterator it = map.keySet().iterator() ; it.hasNext();) {
            String url = it.next();
            if (new AntPathRequestMatcher( url ).matches( request )) {
                return map.get( url );
            }
        }

        return null;
    }

    @Override
    public Collection getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class aClass) {
        return true;
    }

    /**
     * 初始化 所有資源 對(duì)應(yīng)的角色
     */
    public void loadResourceDefine() {
        map = new HashMap<>(16);
        //權(quán)限資源 和 角色對(duì)應(yīng)的表  也就是 角色權(quán)限 中間表
        List rolePermissons = permissionMapper.getRolePermissions();

        //某個(gè)資源 可以被哪些角色訪問
        for (RolePermisson rolePermisson : rolePermissons) {

            String url = rolePermisson.getUrl();
            String roleName = rolePermisson.getRoleName();
            ConfigAttribute role = new SecurityConfig(roleName);

            if(map.containsKey(url)){
                map.get(url).add(role);
            }else{
                List list =  new ArrayList<>();
                list.add( role );
                map.put( url , list );
            }
        }
    }

}

MyInvocationSecurityMetadataSourceService 類實(shí)現(xiàn)了 FilterInvocationSecurityMetadataSource,F(xiàn)ilterInvocationSecurityMetadataSource 的作用是用來儲(chǔ)存請(qǐng)求與權(quán)限的對(duì)應(yīng)關(guān)系。

FilterInvocationSecurityMetadataSource接口有3個(gè)方法:

  • boolean supports(Class clazz):指示該類是否能夠?yàn)橹付ǖ姆椒ㄕ{(diào)用或Web請(qǐng)求提供ConfigAttributes。
  • Collection getAllConfigAttributes():Spring容器啟動(dòng)時(shí)自動(dòng)調(diào)用, 一般把所有請(qǐng)求與權(quán)限的對(duì)應(yīng)關(guān)系也要在這個(gè)方法里初始化, 保存在一個(gè)屬性變量里。
  • Collection getAttributes(Object object):當(dāng)接收到一個(gè)http請(qǐng)求時(shí), filterSecurityInterceptor會(huì)調(diào)用的方法. 參數(shù)object是一個(gè)包含url信息的HttpServletRequest實(shí)例. 這個(gè)方法要返回請(qǐng)求該url所需要的所有權(quán)限集合。

MyAccessDecisionManager

/**
 * 決策器
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {

    private final static Logger logger = LoggerFactory.getLogger(MyAccessDecisionManager.class);

    /**
     * 通過傳遞的參數(shù)來決定用戶是否有訪問對(duì)應(yīng)受保護(hù)對(duì)象的權(quán)限
     *
     * @param authentication 包含了當(dāng)前的用戶信息,包括擁有的權(quán)限。這里的權(quán)限來源就是前面登錄時(shí)UserDetailsService中設(shè)置的authorities。
     * @param object  就是FilterInvocation對(duì)象,可以得到request等web資源
     * @param configAttributes configAttributes是本次訪問需要的權(quán)限
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if (null == configAttributes || 0 >= configAttributes.size()) {
            return;
        } else {
            String needRole;
            for(Iterator iter = configAttributes.iterator(); iter.hasNext(); ) {
                needRole = iter.next().getAttribute();

                for(GrantedAuthority ga : authentication.getAuthorities()) {
                    if(needRole.trim().equals(ga.getAuthority().trim())) {
                        return;
                    }
                }
            }
            throw new AccessDeniedException("當(dāng)前訪問沒有權(quán)限");
        }

    }

    /**
     * 表示此AccessDecisionManager是否能夠處理傳遞的ConfigAttribute呈現(xiàn)的授權(quán)請(qǐng)求
     */
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    /**
     * 表示當(dāng)前AccessDecisionManager實(shí)現(xiàn)是否能夠?yàn)橹付ǖ陌踩珜?duì)象(方法調(diào)用或Web請(qǐng)求)提供訪問控制決策
     */
    @Override
    public boolean supports(Class aClass) {
        return true;
    }

}

MyAccessDecisionManager 類實(shí)現(xiàn)了AccessDecisionManager接口,AccessDecisionManager是由AbstractSecurityInterceptor調(diào)用的,它負(fù)責(zé)鑒定用戶是否有訪問對(duì)應(yīng)資源(方法或URL)的權(quán)限。

MyFilterSecurityInterceptor

@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {

        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //執(zhí)行下一個(gè)攔截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {

        return this.securityMetadataSource;
    }

}

每種受支持的安全對(duì)象類型(方法調(diào)用或Web請(qǐng)求)都有自己的攔截器類,它是AbstractSecurityInterceptor的子類,AbstractSecurityInterceptor 是一個(gè)實(shí)現(xiàn)了對(duì)受保護(hù)對(duì)象的訪問進(jìn)行攔截的抽象類。

AbstractSecurityInterceptor的機(jī)制可以分為幾個(gè)步驟:

    1. 查找與當(dāng)前請(qǐng)求關(guān)聯(lián)的“配置屬性(簡單的理解就是權(quán)限)”
    1. 將 安全對(duì)象(方法調(diào)用或Web請(qǐng)求)、當(dāng)前身份驗(yàn)證、配置屬性 提交給決策器(AccessDecisionManager)
    1. (可選)更改調(diào)用所根據(jù)的身份驗(yàn)證
    1. 允許繼續(xù)進(jìn)行安全對(duì)象調(diào)用(假設(shè)授予了訪問權(quán))
    1. 在調(diào)用返回之后,如果配置了AfterInvocationManager。如果調(diào)用引發(fā)異常,則不會(huì)調(diào)用AfterInvocationManager。

AbstractSecurityInterceptor中的方法說明:

  • beforeInvocation()方法實(shí)現(xiàn)了對(duì)訪問受保護(hù)對(duì)象的權(quán)限校驗(yàn),內(nèi)部用到了AccessDecisionManager和AuthenticationManager;
  • finallyInvocation()方法用于實(shí)現(xiàn)受保護(hù)對(duì)象請(qǐng)求完畢后的一些清理工作,主要是如果在beforeInvocation()中改變了SecurityContext,則在finallyInvocation()中需要將其恢復(fù)為原來的SecurityContext,該方法的調(diào)用應(yīng)當(dāng)包含在子類請(qǐng)求受保護(hù)資源時(shí)的finally語句塊中。
  • afterInvocation()方法實(shí)現(xiàn)了對(duì)返回結(jié)果的處理,在注入了AfterInvocationManager的情況下默認(rèn)會(huì)調(diào)用其decide()方法。

了解了AbstractSecurityInterceptor,就應(yīng)該明白了,我們自定義MyFilterSecurityInterceptor就是想使用我們之前自定義的 AccessDecisionManager 和 securityMetadataSource。

SecurityConfig

@EnableWebSecurity注解以及WebSecurityConfigurerAdapter一起配合提供基于web的security。自定義類 繼承了WebSecurityConfigurerAdapter來重寫了一些方法來指定一些特定的Web安全設(shè)置。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        //校驗(yàn)用戶
        auth.userDetailsService( userService ).passwordEncoder( new PasswordEncoder() {
            //對(duì)密碼進(jìn)行加密
            @Override
            public String encode(CharSequence charSequence) {
                System.out.println(charSequence.toString());
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }
            //對(duì)密碼進(jìn)行判斷匹配
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                boolean res = s.equals( encode );
                return res;
            }
        } );

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/","index","/login","/login-error","/401","/css/**","/js/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage( "/login" ).failureUrl( "/login-error" )
                .and()
                .exceptionHandling().accessDeniedPage( "/401" );
        http.logout().logoutSuccessUrl( "/" );
    }

}

MainController

@Controller
public class MainController {

    @RequestMapping("/")
    public String root() {
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/login-error")
    public String loginError(Model model) {
        model.addAttribute( "loginError"  , true);
        return "login";
    }

    @GetMapping("/401")
    public String accessDenied() {
        return "401";
    }

    @GetMapping("/user/common")
    public String common() {
        return "user/common";
    }

    @GetMapping("/user/admin")
    public String admin() {
        return "user/admin";
    }

}

頁面

login.html




    
    登錄


    

Login page

用戶名或密碼錯(cuò)誤

:
:

index.html





    
    首頁


    

page list

common page
admin page

admin.html



    
    admin page


    success admin page?。?!

common.html



    
    common page


    success common page?。?!

401.html




    
    401 page


    

權(quán)限不夠

拒絕訪問!

最后運(yùn)行項(xiàng)目,可以分別用 user、admin 賬號(hào) 去測試認(rèn)證和授權(quán)是否正確。

參考

《深入理解Spring Cloud與微服務(wù)構(gòu)建》

https://www.ktanx.com/blog/p/4929

源碼

https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security



新聞名稱:SpringBootSecurity詳解
URL鏈接:http://weahome.cn/article/ipioii.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部