您现在的位置是:首页 >技术教程 >SpringSecurity原理和实际应用网站首页技术教程

SpringSecurity原理和实际应用

bobo洁厕灵 2024-06-17 10:20:05
简介SpringSecurity原理和实际应用

前提知识

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

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

实现认证和授权需要以下七张表

所有用户都存在t_user中

给用户授予不同的权限,权限保存在t_premission中

 角色表t_role是便于为用户授权而建立的表,角色就是一堆权限的集合,角色和权限是多对多的关系

页面的全部菜单保存在t_menu中

 以上四个主表通过三张副表实现两两对应关系,且都是多对多关系

用户user和角色role进行关联

角色role和premission关联

角色role和菜单menu关联

角色表有着重要作用,因为都和角色表有多对多关系

用户表ID和角色表ID相关联,关联信息在user_role表中

用户需要不同角色,赋予不同的角色ID

认证过程:只需要用户表就可以了,在用户登录时可以查询用户表t_user进行校验,判断用户输入的用户名和密码是否正确。

授权过程:用户必须完成认证之后才可以进行授权,首先可以根据用户查询其角色,再根据角色查询对应的菜单,这样就确定了用户能够看到哪些菜单。然后再根据用户的角色查询对应的权限,这样就确定了用户拥有哪些权限。所以授权过程会用到上面7张表。

Spring Security可以帮助我们来简化认证和授权

项目中应用

1.在web.xml中配置文件

<!--/*表示安全框架启动之后会扫描所有的请求-->
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>

2.resources提供spring_security配置文件

1.设置拦截规则 

<security:http auto-config="true" use-expressions="true">

    <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />

2.配置认证管理器

<!--认证管理器,用于处理认证操作-->
    <security:authentication-manager>
        <!--认证提供者,执行具体的认证逻辑,此处爆红不影响使用-->
        <security:authentication-provider user-service-ref="springSecurityUserService">
            <!--指定密码加密策略-->
            <security:password-encoder ref="passwordEncoder" />
        </security:authentication-provider>
    </security:authentication-manager>

{noop}表示明文

 项目启动之后

web.xml

<!--/*表示安全框架启动之后会扫描所有的请求-->
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>

spring_security配置文件

<!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的URL
            isAuthenticated():已经经过认证(不是匿名用户),只要是
        -->
        <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />

配置可以匿名访问的资源 

 <!--
        http:用于定义相关权限控制
        指定哪些资源不需要进行权限校验,匿名(不登陆)即可访问以下资源,可以使用通配符
    -->
    <security:http security="none" pattern="/js/**" />
    <security:http security="none" pattern="/css/**" />
    <security:http security="none" pattern="/img/**" />
    <security:http security="none" pattern="/plugins/**" />

使用自己指定的页面作为登录页面

username,password对应登录界面的参数

login-processing-url:登录表单提交的地址,后续不需要创建controller类,框架会自动处理 default-target-url:认证成功跳转界面

authentication-failure-url:失败跳转界面

<!--form-login:定义表单登录信息-->
        <security:form-login login-page="/login.html"
                             username-parameter="username"
                             password-parameter="password"
                             login-processing-url="/login.do"
                             default-target-url="/pages/main.html"
                             always-use-default-target="true"
                             authentication-failure-url="/login.html"
        />

配置crsf项

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

实现从数据库中查询用户

如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

1.创建一个service类

/**
 * 动态给用户授予角色和权限
 */
@Component
public class SpringSecurityUserService implements UserDetailsService {

    @Reference //注意:此处要通过dubbo远程调用用户服务
    private UserService userService;

    //根据用户名查询用户信息
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.findByUsername(username);
        if (user == null) {
            //用户名不存在
            return null;
        }
        //动态为当前用户授权
        List<GrantedAuthority> list = new ArrayList<>();
        Set<Role> roles = user.getRoles();
        for (Role role : roles) {
            //遍历角色,为用户授予角色
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
            Set<Permission> permissions = role.getPermissions();
            for (Permission permission : permissions) {
                //遍历权限集合,为用户授权
                list.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        org.springframework.security.core.userdetails.User securityUser = new org.springframework.security.core.userdetails.User(username,user.getPassword(),list);
        return securityUser;
    }
}
//User是框架提供的User,根据用户名查询数据库信息(包含存储的密码,将用户信息返回给框架,框架会进行密码比对
User user = userService.findByUsername(username);

密码加密

bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题

第一步:在spring-security.xml文件中指定密码加密对象

user1.setPassword(passwordEncoder.encode("admin"));

数据库中的密码是经过加密之后的密码,前端提交的密码会自动加密并和数据库中作比较

记得在xml文件中开启注解控制

配置多种校验规则

<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/index.jsp"  access="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页面-->
<security:intercept-url pattern="/c.html"  access="hasRole('ROLE_ADMIN')" />

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

注解方式权限控制

针对不同页面访问的权限要求,SpringSecurity提供了注解方式权限控制,可以精确到方法控制级别

第一步:在spring-security.xml文件中配置组件扫描,用于扫描Controller

<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="com.itheima.controller"></context:component-scan>

第二步:在spring-security.xml文件中开启权限注解支持

<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />

controller类中的代码

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

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

注意注解代码中的权限必须和数据中权限表的关键字一致

七牛云绑定自定义域名

1.新建存储空间,并使用javaSDK方式操作空间

Java SDK的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access Key和Secret Key。

后端代码

新建上传文件工具类Utils。工具类中需要配置云存储的相关参数,

public class QiniuUtils {
    public  static String accessKey = "YLRQlxIU5IyLEGSbhFt-hAYKvGY0_zNk_eABhM8t";
    public  static String secretKey = "eswtzJogA9qDSne9Z9uZM8Kt8QNS0AYPNS971dxX";
    public  static String bucket = "ahtcm-space-01";

    //上传文件(方法1
    public static void upload2Qiniu(String filePath,String fileName){
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Region.region2());//使用华南机房
        cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;// 指定分片上传版本
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);
        //...生成上传凭证,然后准备上传
        Auth auth = Auth.create(accessKey, secretKey);
        String upToken = auth.uploadToken(bucket);
        try {
            Response response = uploadManager.put(filePath, fileName, upToken);
            //解析上传成功的结果
            DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
            System.out.println("文件名:" + putRet.key);
            System.out.println("文件在云存储中的唯一标识符:" + putRet.hash);
        } catch (QiniuException ex) {
            Response r = ex.response;
            System.err.println(r.toString());
            try {
                System.err.println(r.bodyString());
            } catch (QiniuException ex2) {
                //ignore
            }
        }
    }

    //上传文件(法2
    public static void upload2Qiniu(byte[] bytes, String fileName){
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Region.region2());//使用华南机房
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);

        //默认不指定key的情况下,以文件内容的hash值作为文件名
        String key = fileName;
        Auth auth = Auth.create(accessKey, secretKey);
        String upToken = auth.uploadToken(bucket);
        try {
            Response response = uploadManager.put(bytes, key, upToken);
            //解析上传成功的结果
            DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
            System.out.println("文件名:" + putRet.key);
            System.out.println("文件在云存储中的唯一标识符:" + putRet.hash);
        } catch (QiniuException ex) {
            Response r = ex.response;
            System.err.println(r.toString());
            try {
                System.err.println(r.bodyString());
            } catch (QiniuException ex2) {
                //ignore
            }
        }
    }

    //删除文件
    public static void deleteFileFromQiniu(String fileName){
        //构造一个带指定Zone对象的配置类
        Configuration cfg = new Configuration(Zone.zone0());
        String key = fileName;
        Auth auth = Auth.create(accessKey, secretKey);
        BucketManager bucketManager = new BucketManager(auth, cfg);
        try {
            bucketManager.delete(bucket, key);
        } catch (QiniuException ex) {
            //如果遇到异常,说明删除失败
            System.err.println(ex.code());
            System.err.println(ex.response.toString());
        }
    }
}

在controller中调用工具类方法进行文件上传

//文件上传,图片上传
    @RequestMapping("/upload")
    public Result upload(@RequestParam("imgFile") MultipartFile imgFile) {//使用springmvc的上传组件来上传文件,后台接收到文件
        //System.out.println(imgFile);
        //获取原始文件名
        String originalFilename = imgFile.getOriginalFilename();
        int lastIndexOf = originalFilename.lastIndexOf(".");
        //获取文件后缀
        String suffix = originalFilename.substring(lastIndexOf - 1);
        //使用UUID随机产生文件名称,防止同名文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;
        try {
            QiniuUtils.upload2Qiniu(imgFile.getBytes(), fileName);//使用七牛云工具类的bytes数组上传文件方法,第二个参数为文件名
            //将上传图片名称存入Redis,基于Redis的Set集合存储
            String urlFileName = "http://ruh9fwjil.hn-bkt.clouddn.com/" + fileName;
            jedisPool.getResource().sadd(RedisConstant.RECORD_PIC_RESOURCES,urlFileName);
            //图片上传成功
            Result result = new Result(true, MessageConstant.PIC_UPLOAD_SUCCESS);
            result.setData(fileName);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            //图片上传失败
            return new Result(false, MessageConstant.PIC_UPLOAD_FAIL);
        }
    }

如果网络地址配置正确,但前端不能实时查看到文件,去云平台查看是否是将空间设置为了私有空间,私有空间中的bucket不能被直接访问。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。