Spring Security框架的简单使用(权限控制,登录,登录加密)

认证和授权概念

认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。

授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。

常用的权限框架:Spring Security,还有Apache的shiro框架。

Spring Security的使用

Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。官网:https://spring.io/projects/spring-security

 

对应的maven坐标:

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>

工程搭建:这里不再体现了,就是创建一个maven工程,加上依赖,打war包,配置xml文件,下面就是xml文件要位置的东西,固定,看下注释就可以了,部分爆红不要急,先跑,跑不通再看,缺什么补什么

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">

    <display-name>Archetype Created Web Application</display-name>
    <filter>
        <!--
         1:DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件)
          整合Spring Security时过滤器的名称必须为springSecurityFilterChain,
          否则会抛出NoSuchBeanDefinitionException异常
        -->
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 2:springmvc的核心控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

配置spring-security.xml

这里也比较乱,看配置文件注解吧,想要测试的话,必须跟下面的SpringSecurityUserService类配合使用

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd
                  http://www.springframework.org/schema/mvc
                  http://www.springframework.org/schema/mvc/spring-mvc.xsd
                  http://code.alibabatech.com/schema/dubbo
                  http://code.alibabatech.com/schema/dubbo/dubbo.xsd
                  http://www.springframework.org/schema/context
                  http://www.springframework.org/schema/context/spring-context.xsd
                          http://www.springframework.org/schema/security
                          http://www.springframework.org/schema/security/spring-security.xsd">
    <!--1:定义哪些链接可以放行-->
    <!--
      http:用于定义相关权限控制
      指定哪些资源不需要进行权限校验,可以使用通配符
    -->
    <security:http security="none" pattern="/css/**"/>
    <security:http security="none" pattern="/img/**"/>
    <security:http security="none" pattern="/js/**"/>
    <security:http security="none" pattern="/aaa.html" />
    <security:http security="none" pattern="/login.html"/>


    <!--
        2:定义哪些链接不可以放行,即需要有角色、权限才可以放行
        http:用于定义相关权限控制
        auto-config:是否自动配置
                        设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                        设置为false时需要显示提供登录表单配置,否则会报错
        use-expressions:用于指定intercept-url中的access属性是否使用表达式
    -->
    <security:http auto-config="true" use-expressions="true">
        <!--
        当前用户访问/**资源 需要有ROLE_ADMIN角色权限
        /** :http://localhost:82/a/b/c.html
        security:intercept-url配置有顺序的
        -->

        <!--只要认证通过就可以访问
        isAuthenticated():用户已经认证成功就可以访问此页面
        -->
        <security:intercept-url pattern="/a.html"  access="isAuthenticated()" />

        <!--拥有add权限就可以访问b.html页面-->
        <security:intercept-url pattern="/b.html"  access="hasAuthority('add')" />

        <!--拥有ROLE_ADMIN角色就可以访问c.html页面,
            注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
        <security:intercept-url pattern="/c.html"  access="hasRole('ADMIN')" />
        <!--
                <security:intercept-url pattern="/c.html"  access="hasRole('ROLE_ADMIN')" />
        -->

        <!--拥有ROLE_ABC角色就可以访问d.html页面-->
        <security:intercept-url pattern="/d.html"  access="hasRole('ABC')" />



        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
              请求的用户只需拥有其中的一个角色就能成功访问对应的URL
              注意个配置的顺序,如果定义具体访问某个页面的某种权限配置写在这行代码后面,那么后面的具体指定权限会没有用,
              所有页面只要通过这行代码的权限就可以访问
        -->
        <security:intercept-url pattern="/**" access="hasRole('isAuthenticated()')"/>


        <!--
form-login:定义表单登录信息
        login-page="/login.html":表示指定登录页面
        username-parameter="username":使用登录名的名称,默认值是username
        password-parameter="password":使用登录名的密码,默认值是password
        login-processing-url="/login.do":表示登录的url地址
        default-target-url="/index.html":登录成功后的url地址
        authentication-failure-url="/login.html":认证失败后跳转的url地址,失败后指定/login.html
        always-use-default-target="true" :不管访问哪个页面被拦截需要登录时,都跳转默认指定的页面,这里默认登录成功(index.html)的页面
        如果不配置这个的话,你访问一个不存在的页面,被拦截登录,登录成功后就会直接访问这个不存在的页面,从而报404错误
-->
        <security:form-login login-page="/login.html"
                             username-parameter="username"
                             password-parameter="password"
                             login-processing-url="/login.do"
                             default-target-url="/index.html"
                             authentication-failure-url="/login.html"
                             always-use-default-target="true"
        />


        <!--登出配置
        logout-url="/logout.do" 登出请求
        invalidate-session="true" 失效用户信息
        logout-success-url="/login.html" 登出成功页面
        -->
        <security:logout logout-url="/logout.do" invalidate-session="true" logout-success-url="/login.html"/>


        <!--
        csrf:对应CsrfFilter过滤器
        disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
        -->
        <security:csrf disabled="true"/>
    </security:http>


    <!--
        3:认证管理,定义登录账号名和密码,并授予访问的角色、权限
        authentication-manager:认证管理器,用于处理认证操作
    -->
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
            user-service-ref:指定自己写的类,这个类里面会获取数据库查询到的角色权限,然后存入SimpleGrantedAuthority中,
            再将impleGrantedAuthority存入User(user不是自己的pojo)中,返回这个user,user会自动匹配密码是否正确以及相应权限
        -->
        <security:authentication-provider user-service-ref="springSecurityUserService">
            <!--指定密码加密策略,这个策略需要在spring容器中,这里下文有相应的bean-->
            <security:password-encoder ref="passwordEncoder"></security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>
    <!--扫描包内带@Component注解的类,并将这个类创建到spring核心容器中-->
    <context:component-scan base-package="com.itheima"/>
    <!--配置密码加密对象,id是唯一标识,谁要用这个对象就指向这个唯一标识就可以了-->
    <bean id="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

    <!--启用权限注解,只有启动它才能在方法用注解分配权限-->
    <security:global-method-security pre-post-annotations="enabled"/>
</beans>

 

 

 

//加这个注解,这个类才会被创建到spring核心容器,另外这个类必须实现UserDetailsService
@Component
public class SpringSecurityUserService implements UserDetailsService {
    //模拟数据库中的用户数据
    public static Map<String, User> map = new HashMap<String, User>();
    //这里只是为了模拟从数据库取出来密码,所以要用它进行加密操作
    public static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    static {
        com.itheima.pojo.User user1 = new com.itheima.pojo.User();
        user1.setUsername("admin");
        user1.setPassword("admin2");

        com.itheima.pojo.User user2 = new com.itheima.pojo.User();
        user2.setUsername("zhangsan");
        user2.setPassword("123");

        map.put(user1.getUsername(), user1);
        map.put(user2.getUsername(), user2);
    }
    /**
     * 根据用户名加载用户信息
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("username:" + username);
        com.itheima.pojo.User userInDb = map.get(username);//模拟根据用户名查询数据库
        if (userInDb == null) {
            //根据用户名没有查询到用户,抛出异常,表示登录名输入有误
            return null;
        }

        //模拟数据库中的密码,后期需要查询数据库
        /*String passwordInDb = "{noop}" + userInDb.getPassword();*/
        String passwordInDb = passwordEncoder.encode(userInDb.getPassword());
        System.out.println(passwordInDb);

        List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
        //授权,后期需要改为查询数据库动态获得用户拥有的权限和角色,这里注意,如果添加的是角色,则必须以ROLE_开头,并且全部都是大写,
        //否则它就会把这个角色当成权限来匹配,如果没有对应的权限的话,你访问不到该角色应该有的功能。原则上加不加没关系,这里只是为了区分角色和权限
        list.add(new SimpleGrantedAuthority("add")); // 权限
        list.add(new SimpleGrantedAuthority("delete")); // 权限
        list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // 角色
        list.add(new SimpleGrantedAuthority("ROLE_ABC")); // 角色
        //返回User,参数一:存放登录名,参数二:存放数据库查询的密码(数据库获取的密码,默认会和页面获取的密码进行比对,成功跳转到成功页面,失败回到登录页面,并抛出异常表示失败),存放当前用户具有的角色
        UserDetails user = new org.springframework.security.core.userdetails.User(username, passwordInDb, list);
        return user;
    }


    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String s = encoder.encode("abc");
        System.out.println(s);
        String s1 = encoder.encode("abc");
        System.out.println(s1);
        /*$2a$10$.NZ4a38pAVUE0ahegdnDnOdXJGxm/yhvSUmW9JOktqQmzuIxrNtmK
        $2a$10$wuj158lZiSdEEBKg8gjUwOmIlWeGu6B0JXrDZ8XycyGP8sSBDk0Yi*/
        // 进行判断
        boolean b = encoder.matches("abc", "$2a$10$.NZ4a38pAVUE0ahegdnDnOdXJGxm/yhvSUmW9JOktqQmzuIxrNtmK");
        System.out.println(b);
    }

}
/**
 * 权限控制-注解方式(针对controller权限控制)
 */
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/add")
    @PreAuthorize("hasAuthority('delete')")//表示用户必须拥有add权限才能调用当前方法
    public String add(){
        System.out.println("add...");
        return null;
    }

    @RequestMapping("/update")
    @PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法
    public String update(){
        System.out.println("update...");
        return null;
    }

    @RequestMapping("/delete")
    @PreAuthorize("hasRole('ABC')")//表示用户必须拥有ABC角色才能调用当前方法
    public String delete(){
        System.out.println("delete...");
        return null;
    }
}

 

好吧,些许潦草,简单总结一下:

1、使用这个框架,可以对用户输入的密码进行加密,并把加密后的密文存入到数据库中

2、通过上面的方法,可以将数据库中查询到的用户名、密码(密文)、角色、权限全部扔到SimpleGrantedAuthority和框架里面的org.springframework.security.core.userdetails.User类中,直接返回这个类,就可以自动校验密码,正确访问什么页面,密码错误访问什么页面,正确以后会确定权限,确定权限方式两种,第一种是配置文件中的对页面进行权限控制,里面直接指定了某个页面需要什么权限什么角色才能访问,第二种是在controller类中、方法中添加相应的注解,框架底层会自动匹配相应权限和角色,匹配上则让你处理这个请求,否则就不会。

3、注册的时候会使用到加密,登录不需要加密,因为注册时往数据库中存储的就是密文,只需要把查出来的密文扔进框架就可以了。

4、总的来说,这个框架的功能大体上就是一个分配权限和密码加密,它可以把所有页面请求都拦截,强行让你登录,登录成功就会给你分配权限,你的账号有什么权限就只能做什么操作。

5、密文我也不太懂,大概流程就是,客户端给服务器传过去的就是一串字符串,服务器接收到以后,框架底层自动将字符串加密成密文,并从数据库中取出来的密文中,取出注册时动态加密的盐,然后拼接,并跟数据库中的密文匹配,成功就对了,总之,它绝对安全,除非别人能够获取到数据库中存储的密文,否则绝对解不了。

 6、还有一个问题,就是这个框架它默认是不信任嵌套页面,也就是html中的iframe标签,所以如果没有特别设置的话,它就会拦截访问,这里加下面这个配置就好了

    <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
    <security:headers>
        <security:frame-options policy="SAMEORIGIN"></security:frame-options>
    </security:headers>

 

这个一定要加在security:http标签里面,也就是配置intercept-url:定义一个拦截规则的前面

 

7、最后一个小逻辑,就是获取当前用户信息返回给前端,因为前端需要它来展示当前是哪个用户在登录,这是通过前端通过判断以后,确定登录成功了,然后发请求,后端框架中就可以直接获取到存放登录信息的对象,返回给前端就可以了

/**
 * 用户控制层
 */
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 获取用户名
     */
    @RequestMapping(value = "/getUserName",method = RequestMethod.GET)
    public Result getUserName(){
        try {
            User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String username = user.getUsername();
            return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(false, MessageConstant.GET_USERNAME_FAIL);
        }
    }
}

 

posted @ 2020-10-18 22:02  名难  阅读(1575)  评论(0编辑  收藏  举报