Spring Secirty的使用
1.介绍
Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证
和授权的过程
官网: https://spring.io/projects/spring-security.
2.配置
2.1.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>
2.2.配置Mvc中的web.xml文件
在web.xml中主要配置SpringMVC的DispatcherServlet和用于整合第三方框架的DelegatingFilterProxy,用于整合Spring Security。
<filter>
<!--
DelegatingFilterProxy用于整合第三方框架
整合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>
<servlet>
<servlet-name>servlet的别名A</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表示servlet应该被载入的顺序
当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servlet的别名A</servlet-name>
<!--
*.xxx扩展名匹配,这里表示匹配全部以do结尾的请求
-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2.3 .配置spring-security.xml
在spring-security.xml中主要配置Spring Security的拦截规则和认证管理器。
<?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">
<!--
http:用于定义相关权限控制
auto-config:是否自动配置
设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出 处理等
设置为false时需要显示提供登录表单配置,否则会报错
use-expressions:用于指定intercept-url中的access属性是否使用表达式
-->
<security:http auto-config="true" use-expressions="true">
<!--
intercept-url: 定义一个拦截规则
pattern: 对哪些url进行权限控制
access: 在请求对应的url时需要什么权限,默认配置时它应该是一个以,分隔的角色列表
请求的用户只需要拥有其中一个角色就能成功访问到对应的url
-->
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
<!--access配置所有的属性值
hasIpAddress(ipAddress): 用于匹配一个请求的ip地址或者一个地址的网络掩码
例:access="hasIpAddress('162.0.0.0/224')"
hasRole(role): 用于匹配一个使用GrantedAuthority(已授予的权限)的角色(类似RoleVoter)
RoleVoter是Spring Security内置的一个AccessDecisionVoter
(AccessDecisionVoter 是一个投票器,负责对授权决策进行表决。
然后,最终由唱票者AccessDecisionManager 统计所有的投票器表决后,来做最终的授权决策。)
例:access="hasRole('ROLE_USER')"
hasAnyRole(role): 用于匹配一个使用GrantedAuthority的角色列表。用户匹配其中任何一个均可放行
例:access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"
-->
</security:http>
<!--
authentication-manager: 认证管理器,用于处理认证操作
-->
<security:authentication-manager>
<!--
authentication-provider: 认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider>
<!--
user-service: 用于获取用户信息,提供给authentication-provider进行认证
-->
<security:user-service>
<!--
user: 定义用户信息,可以指定用户名、密码、角色,
(也可以直接从数据库中查询用户信息不用定义)
{noop}: 表示当前使用的密码为明文
authorities: 表示这个用户所拥有的角色
-->
<security:user name="admin"
password="{noop}admin"
authorities="ROLE_ADMIN">
</security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<beans>
2.3.1.spring-security.xml的改进地方
2.3.1.1.配置可匿名访问的资源
不是所有的url我们都进行拦截加上权限控制,有些url我们认为它可以匿名访问(无需登录就能访问)
<!--
配置哪些资源匿名可以访问(不登录也可以访问),
这里我们定义一个登录页面可以匿名访问
///
这里我是配置在auto-config之前
-->
<security:http security="none" pattern="/login.html"></security:http>
<security:http security="none" pattern="/css/**"></security:http>
<security:http security="none" pattern="/js/**"></security:http>
2.3.1.2.我们需要一个专门的登录页面
我们需要一个专门的登录页面
<!--
对应上面的匿名访问的login.html,这里我们自定义一个简陋的登录页面(这个还是得看前端大爷的)
-->
<html>
<head>
<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>
2.3.1.3.这个对登录和退出登录的配置修改
<!--
auto-config:自动配置,如果设置为true,表示自动应用一些默认配置,比如框架会提供一个默认的登录页面
use-expressions:是否使用spring security提供的表达式来描述权限
-->
<security:http auto-config="true" use-expressions="true">
<security:headers>
<!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
<security:frame-options policy="SAMEORIGIN"></security:frame-options>
</security:headers>
<!--配置拦截规则,/** 表示拦截所有请求-->
<!--
pattern:描述拦截规则
asscess:指定所需的访问角色或者访问权限
-->
<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/pages/**" access="isAuthenticated()" />
<!--如果我们要使用自己指定的页面作为登录页面,必须配置登录表单.页面提交的登录表单请求是由框架负责处理-->
<!--
login-page: 指定登录页面访问URL
login-processing-url: 我们自定义的登录用户验证路径
default-target-url: 身份验证登陆成功后发送的请求
authentication-failure-url : 身份验证登陆失败后发送的请求
-->
<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"></security:form-login>
<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项
(配置 disabled="true"就行了),否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"></security:csrf>
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
invalidate-session:否销毁Session
-->
<security:logout logout-url="/logout.do"
logout-success-url="/login.html"
invalidate-session="true"/>
</security:http>
2.3.1.4.从数据库中读取用户数据
之前是直接将用户名和密码配置在了配置文件中,而实际生活中我们的用户名和密码往往保存在数据库
如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。
1.创建实体类
public class User implements Serializable{
private Integer id; // 主键
private Date birthday; // 生日
private String gender; // 性别
private String username; // 用户名,唯一
private String password; // 密码
private String remark; // 备注
private String station; // 状态
private String telephone; // 联系电话
private Set<Role> roles = new HashSet<Role>(0);//对应角色集合
为了节省篇幅,省去了set,get方法
}
2.自定义的SpringSecurityUserService 实现UserDetailsService接口,来完成授权和验证
@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){
//非空,则说明用户存在
//通过用户获取角色
Set<Role> roles = user.getRoles();
//创建权限集合,方便调用
List<GrantedAuthority> list = new ArrayList<>();
//遍历所有的用户角色
for (Role role : roles) {
//授予角色
//keyword; 权限关键字,用于权限控制
list.add(new SimpleGrantedAuthority(role.getKeyword()));
//获取角色的权限
Set<Permission> permissions = role.getPermissions();
//遍历所有的权限
for (Permission permission : permissions) {
// 把权限注入到角色中(授权)
list.add(new SimpleGrantedAuthority(permission.getKeyword()));
// keyword; 权限关键字,用于权限控制
//SimpleGrantedAuthority是基于
// GrantedAuthority权限的授权方式
}
}
org.springframework.security.core.userdetails.User securityUser = new org.springframework.security.core.userdetails.User(username,user.getPassword(),list);
return securityUser;
}else {
return null;
}
}
}
3.1UserDao层以及sql语句
public interface UserDao {
// 通过用户名查询用户id(通过用户名判断是否存在用户,有id就有用户,没有id就没有用户)
public User findByUsername(String username);
}
//分开的,sql语句放在resources包下的UserDao.xml中
<mapper namespace="com.dao.UserDao">
<select id="findByUsername" parameterType="string" resultType="com.POJO.User">
select * from t_user where username = #{username}
</select>
</mapper>
3.2RoleDao以及sql语句
public interface RoleDao {
//通过用户id把角色赋给相应的用户
(这里角色相当于职位,permission权限,相当于其(role)职位的权利(权限)
public Set<Role> findRoleByUserId(Integer userId);
}
//sql语句
<mapper namespace="com.dao.RoleDao">
<select id="findRoleByUserId" parameterType="int" resultType="com.POJO.Role">
SELECT r.* FROM t_role r , t_user_role ur WHERE r.id = ur.role_id AND ur.user_id = #{user_id}
</select>
</mapper>
3.3PermissionDao以及sql语句
public interface PermissionDao {
//给相应的角色(可以理解为职位),赋予相应的权利
public Set<Permission> findPermissionByRoleId(Integer roleId);
}
//sql语句
<mapper namespace="com.dao.PermissionDao">
<select id="findPermissionByRoleId" parameterType="int" resultType="com.POJO.Permission">
SELECT p.* FROM t_permission p , t_role_permission rp
WHERE p.id = rp.permission_id AND rp.role_id = #{permission_id}
</select>
</mapper>
4.service层以及其实现类
public interface UserService {
public User findByUsername(String username);
}
// UserService的实现类
@Service(interfaceClass = UserService.class)
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private PermissionDao permissionDao;
public User findByUsername(String username) {
//通过username查询,来确认是否存在用户
User user = userDao.findByUsername(username);
if (user != null){
//user不为null,说明user存在
//通过用户获取userId,以此查询角色(相当于职位)和permission相应权限
//即,根据用户ID查询对应的角色
Integer userId = user.getId();
Set<Role> roles = roleDao.findRoleByUserId(userId);
user.setRoles(roles);//让用户关联角色
if (roles != null){
for (Role role : roles) {
//循环遍历角色
Integer roleId = role.getId();
//根据角色ID查询关联的权限
Set<Permission> permissions = permissionDao.findPermissionByRoleId(roleId);
//让角色关联相应权限
role.setPermissions(permissions);
}
}
}else {
return null;
}
return user;
}
}
5.cotroller层代码
@RestController
@RequestMapping("/user")
public class UserController {
//获取当前登录(认证)用户的用户名
@RequestMapping("/getLoginUsername")
public Result getLoginUsername(){
try{
//通过Authentication.getPrincipal()可以获取到代表当前用户的信息,
//这个对象通常是UserDetails的实例。获取当前用户的用户名是一种比较常见的需求。
//此外,调用SecurityContextHolder.getContext()是获取SecurityContext,
//如果对应的SecurityContext不存在,则Spring Security将为我们建立一个空的SecurityContext并进行返回。
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();//获取用户信息
String username = user.getUsername();
String password = user.getPassword();
Collection<GrantedAuthority> authorities = user.getAuthorities();
return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username);
}catch (Exception e){
return new Result(false, MessageConstant.GET_USERNAME_FAIL);
}
}
}
2.1.1.5由于{noop}的缘故,密码是明文状态,并不安全,正常都是加密的
<!--配置认证管理器-->
<security:authentication-manager>
<!--更改Spring-Security.xml中身份验证的方式,使用自定义的UserDetailsService-->
<security:authentication-provider user-service-ref="springSecurityUserService">
<!--指定密码加密策略-->
<security:password-encoder ref="passwordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
<!--配置密码加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
开启注解方式后,我们就能通过添加@PreAuthorize(“hasAuthority(‘xxx权限’)”)注解来权限校验