一、spring security 简介
- 认证:(你是谁)身份认证,使用系统的任何用户,系统对于他来讲必须要能够进行识别,这个识别的过程我们就称之为认证。在此过程中牵涉到两个表,用户表和角色表,他们能够确定用户的权限。
- 授权(你能干什么):对于一个指定的用户,系统必须能够知道他具体有什么权限。权限表能够确定用户拥有什么权限。一个完整的系统,必须能够进行认证和鉴权,否则系统不具有安全性,系统的功能也不具有任何意义!
- 攻击防护:(防止伪造身份)
<!--设置不拦截这个页面!-->
<security:http security="none" pattern="/pages/a.html"/>
<!--使用自动配置-->
<security:http auto-config="true" use-expressions="true">
<!--对哪些请求进行拦截,对所有的请求进行拦截,只有ROLE_ADMIN的角色才拥有所有的访问权限-->
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
<!--授权管理器,用来鉴权,鉴定什么用户拥有什么权限-->
<security:authentication-manager>
<!--提供者-->
<security:authentication-provider>
<!--我们给他的鉴权提供者-->
<security:user-service>
<!--直接在配置文件中指定用户名,{noop}表示是一个明文密码,
到时候框架进行密码比对就不会进行加密比对,直接明文比对,authorities指定权限-->
<security:user name="admin" password="{noop}123" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
以上配置没有指定需要使用的登录页面,可认证的用户在文件中写死了,访问相关资源时,由框架提供默认的登陆页面,其他用户都不可用!
输入错误密码:
输入正确的密码:
直接访问http://localhost:88/pages/a.html:
结果是可以直接进去,因为spring-security配置了不对a.html进行拦截
二、自定义登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义登录页面</title>
</head>
<body>
<form action="/login.do" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="submit">
</form>
</body>
</html>
配置文件:
<security:http security="none" pattern="/login.html"/>
<!--使用自动配置-->
<security:http auto-config="true" use-expressions="true">
<!--对哪些请求进行拦截,对所有的请求进行拦截,只有ROLE_ADMIN的角色才拥有所有的访问权限-->
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
<!--指定自己的登录页面-->
<!--用户名-->
<!--密码-->
<!--登陆页面-->
<!--目标url:登陆成功后访问的页面-->
<!--登录成功后form表单提交执行的命令-->
<!--登陆失败后访问的页面-->
<security:form-login username-parameter="username"
password-parameter="password"
login-page="/login.html"
default-target-url="/index.jsp"
login-processing-url="/login.do"
authentication-failure-url="/login.html"/>
<!--不要检查当前是不是非法或者是伪造的请求,这个不配即使用户名密码是对的也不能登录成功-->
<security:csrf disabled="true"/>
</security:http>
案例一:输入错误的密码
结果:输入错误的密码后他并不会显示提示信息,但是失败后还会跳转到登录页面!
案例二:输入正确的密码
结果:完成登录!
三、如何使固定的账号密码变成动态的从数据库中拿到?
答案是:使用user-service-ref来得到外部引入的bean,该bean是我们开发人员自己手动实现的!
UserService.class
/**
* @author Dragon code!
* @create 2022-04-12 10:41
*/
public class UserService implements UserDetailsService {
//定义一个map
private Map<String,User> map = new HashMap<>();
//模拟从数据库中查询到的用户
private void initDate(){
User user1 = new User();
user1.setUsername("admin");
user1.setPassword("123");
User user2 = new User();
user2.setUsername("lay");
user2.setPassword("456");
map.put(user1.getUsername(),user1);
map.put(user2.getUsername(),user2);
}
//通过用户名得到用户对象
//当用户登录后进行提交会调用这个方法,该方法可以得到用户输入的用户名,注意是输入的。
//基于用户名到表中查询用户表,然后返回当前用户的身份,包含名称和密码,用user对象进行封装,
//如果未返回用户的身份表示该用户其实是不存在的那么必然通不过认证,此时方法应该返回null
//如果用户存在,此时框架(下面的方法)就要进行密码的比对,判断是否正确,所以返回的对象中就必须要包含从数据库中查询出来的用户名和密码,以便框架比对
//如果密码验证通过,此时进行鉴权!就是检查当前用户的角色和权限,所以该对象中还需要一份包含了当前用户的角色和权限的list
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username="+username);
initDate();
User userInDb = map.get(username);//实际项目中此处应该调用service的查询用户的功能!而不是模拟map
if (userInDb == null){
return null;
}
String passwordInDb = "{noop}"+userInDb.getPassword();
//此泛型还是一个接口 ,所以里面要给他的实现类对象作为集合的元素
List<GrantedAuthority> list = new ArrayList<>();
if (userInDb.getUsername().equals("admin")){
//该对象的参数名为role,意思是当前用户的角色到底是什么
list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
if (userInDb.getUsername().equals("lay")){
//注意此时给的不是角色 而是add权限!由开发者自己指定是角色名还是权限名。
//通过角色名还是通过权限名进行访问完全是由开发者决定的
list.add(new SimpleGrantedAuthority("add"));
}
//由于此方法要求返回一个userdetails对象,所以必须返回security的user对象。
UserDetails userDetails = new org.springframework.security.core.userdetails.User(userInDb.getUsername(),passwordInDb,list);
return userDetails;
}
}
写死方式:
动态方式:
<!--提供者-->
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
<bean id="userService" class="com.lay.security.UserService"/>
案例演示:
- 使admin用户可以访问index.jsp首页面,但是访问不了a.html
- 是lay用户可以访问a.html页面但是访问不了首页面
service实现层代码见上方UserService.class
配置文件的修改如下:spring-security.xml
注意点:
- 关闭对于login页面的不拦截处理!
- 通过security:intercept-url来对不同的页面进行角色或者是权限的验证,注意access中对二者字符串的不同处理不要搞错!
<?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: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/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--设置不拦截这个页面!-->
<!-- <security:http security="none" pattern="/pages/a.html"/>-->
<security:http security="none" pattern="/login.html"/>
<!--使用自动配置-->
<security:http auto-config="true" use-expressions="true">
<!--对哪些请求进行拦截,对所有的请求进行拦截,只有ROLE_ADMIN的角色才拥有所有的访问权限-->
<!--带hasRole表示角色-->
<!--此时表示可以通过指定权限和角色进行访问-->
<security:intercept-url pattern="/index.jsp" access="hasRole('ROLE_ADMIN')"/>
<!--带hasAuthority表示访问a.html必须有add权限-->
<security:intercept-url pattern="/pages/a.html" access="hasAuthority('add')"/>
<!--指定自己的登录页面-->
<!--用户名-->
<!--密码-->
<!--登陆页面-->
<!--目标url:登陆成功后访问的页面-->
<!--登录成功后form表单提交执行的命令-->
<!--登陆失败后访问的页面-->
<security:form-login username-parameter="username"
password-parameter="password"
login-page="/login.html"
default-target-url="/index.jsp"
login-processing-url="/login.do"
authentication-failure-url="/login.html"/>
<!--不要检查当前是不是非法或者是伪造的请求,这个不配即使用户名密码是对的也不能登录成功-->
<security:csrf disabled="true"/>
</security:http>
<!--授权管理器,用来鉴权,鉴定什么用户拥有什么权限-->
<security:authentication-manager>
<!--提供者-->
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
</security:authentication-manager>
<bean id="userService" class="com.lay.security.UserService"/>
</beans>
对于service类的解释:
重点在下面这段代码:
- 返回对象是securitey自带的User对象,而不是我们写的!
- 该对象包含了用户名和密码,然后还有包含如
new SimpleGrantedAuthority("add")对象的list对象!
该对象的实质就是当前用户的权限或者角色
- 最后将方法的userDetails对象返回出去,至此不同情况下的拦截完成!
//由于此方法要求返回一个userdetails对象,所以必须返回security的user对象。
UserDetails userDetails = new org.springframework.security.core.userdetails.User(userInDb.getUsername(),passwordInDb,list);
效果如下:
一、登录admin账号
访问首页面成功!
访问a.html失败!
二、登录lay账号
首页面拦截成功!
a.html成功访问!
四、密码加密:
<!--授权管理器,用来鉴权,鉴定什么用户拥有什么权限-->
<security:authentication-manager>
<!--提供者-->
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--加密工具,只能进行加密不能解密-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--包含了注解扫描功能-->
<context:annotation-config></context:annotation-config>
更新init方法
//注入加密工具类
@Autowired
private PasswordEncoder passwordEncoder;
//模拟从数据库中查询到的用户
private void initDate(){
User user1 = new User();
user1.setUsername("admin");
user1.setPassword(passwordEncoder.encode("123"));
User user2 = new User();
user2.setUsername("lay");
user2.setPassword(passwordEncoder.encode("456"));
map.put(user1.getUsername(),user1);
map.put(user2.getUsername(),user2);
}
五、注解的方式实现对方法的拦截
<?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:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--注解驱动和包扫描-->
<mvc:annotation-driven></mvc:annotation-driven>
<context:component-scan base-package="com.lay.security">
</context:component-scan>
<!--设置不拦截这个页面!-->
<!-- <security:http security="none" pattern="/pages/a.html"/>-->
<security:http security="none" pattern="/login.html"/>
<!--使用自动配置-->
<security:http auto-config="true" use-expressions="true">
<!--只要登录认证通过就可以访问-->
<security:intercept-url pattern="/index.jsp" access="isAuthenticated()"/>
<security:intercept-url pattern="/pages/a.html" access="isAuthenticated()"/>
<!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/pages/b.html" access="hasAuthority('add')"/>
<!--拥有ROLE_ADMIN角色就可以访问c.html页面-->
<security:intercept-url pattern="/pages/c.html" access="hasRole('ROLE_ADMIN')"/>
<!--拥有ROLE_ADMIN角色就可以访问d.html页面,
注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
<security:intercept-url pattern="/pages/d.html" access="hasRole('ADMIN')"/>
<!--指定自己的登录页面-->
<!--用户名-->
<!--密码-->
<!--登陆页面-->
<!--目标url:登陆成功后访问的页面-->
<!--登录成功后form表单提交执行的命令-->
<!--登陆失败后访问的页面-->
<security:form-login username-parameter="username"
password-parameter="password"
login-page="/login.html"
default-target-url="/index.jsp"
login-processing-url="/login.do"
authentication-failure-url="/login.html"/>
<!--只要发这个请求,当前用户就退出来认证,退出成功访问的页面,退出后要不要让session失效-->
<security:logout logout-url="/logout.do"
logout-success-url="/login.html"
invalidate-session="true"/>
<!--不要检查当前是不是非法或者是伪造的请求,这个不配即使用户名密码是对的也不能登录成功-->
<security:csrf disabled="true"/>
</security:http>
<!--授权管理器,用来鉴权,鉴定什么用户拥有什么权限-->
<security:authentication-manager>
<!--提供者-->
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!--设置要添加前缀注解,配置后可以在控制器类中使用注解来实现对方法的拦截-->
<security:global-method-security pre-post-annotations="enabled"/>
<bean id="userService" class="com.lay.security.UserService"/>
<!--加密工具,只能进行加密不能解密-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
</beans>
控制器类:
核心是@PreAuthorize这个注解:它规定了只有带有某个权限或者是角色才可以访问此方法!否则浏览器403错误。
/**
* @author Dragon code!
* @create 2022-04-12 15:30
*/
@RestController
@RequestMapping("/security")
public class MyController {
//该注解的使用还必须在配置文件中配置
//<security:global-method-security pre-post-annotations="enabled"/>
//设置请求是否能访问方法
@PreAuthorize("hasAuthority('add')")
@RequestMapping("/add")
public String add(){
return "add success!";
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/delete")
public String delete(){
return "delete success!";
}
}