前提知识
认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。
授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。
实现认证和授权需要以下七张表
所有用户都存在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不能被直接访问。