Spring Security前身是Acegi Security,是Spring项目组中用来提供安全服务的框架
两个主要操作
认证
:用户建立一个他所声明的主体。主体一般是指用户,设备或可以在你系统中执行动作的其他系统。可以使用xml配置文件认证或者使用数据库认证
授权
:指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主体已经有身份验证过程建立了。
基础入门
1–导入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
web.xml文件创建filter(名称必须为:springSecurityFilterChain 不可改变)
<?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_2_5.xsd"
version="2.5">
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<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>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
(使用xml配置文件认证)spring security核心配置文件配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="/login.html" />
<security:http security="none" pattern="/failer.html" />
<security:http auto-config="true" use-expressions="false" >
<!-- 配置资料连接,表示任意路径都需要ROLE_USER权限 -->
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!--自定义登陆页面,
login-page 自定义登陆页面
authentication-failure-url 用户权限校验失败之后才会跳转到这个页面,如果数据库中没有这个用户则不会跳转到这个页面。
default-target-url 登陆成功后跳转的页面。 注:登陆页面用户名固定 username,密码 password,action:login
-->
<security:form-login login-page="/login.html"
login-processing-url="/login"
username-parameter="username"
password-parameter="password"
authentication-failure-url="/failer.html"
default-target-url="/success.html"
authentication-success-forward-url="/success.html"
/>
<!-- 登出, invalidate-session 是否删除session logout-url:登出处理链接 logout-success-url:登出成功页面
注:登出操作 只需要链接到 logout即可登出当前用户 -->
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
<!-- 关闭CSRF,默认是开启的 -->
<security:csrf disabled="true" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service><!--以下创建了两个用户,指定了用户名、密码以及角色-->
<security:user name="user" password="{noop}user" authorities="ROLE_USER" />
<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
(使用数据库认证,即用户存在数据库中)有很多种,这里使用UserDetails、UserDetailsSevice来完成操作
在Spring Security中如果想要使用数据进行认证操作,有很多种操作方式,这里我们介绍使用UserDetails、UserDetailsService来完成操作。
UserDetails
:UserDetails是一个接口,我们可以认为UserDetails作用是于封装当前进行认证的用户信息但由于其是一个接口,所以我们可以对其进行实现,也可以使用Spring Security提供的一个UserDetails的实现类User来完成操作
//UserDetails接口
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
//user类部分代码
public class User implements UserDetails, CredentialsContainer {
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired; //帐户是否过期
private final boolean accountNonLocked; //帐户是否锁定
private final boolean credentialsNonExpired; //认证是否过期
private final boolean enabled; //帐户是否可用
}
//UserDetailsService接口代码
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
例子:用户管理中用户登录来完成Spring Security的认证操作
1-web.xml配置文件配置Security过滤器
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml,classpath*:spring-security.xml</param-value>
</context-param>
<filter>
<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>
<!-- 配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2-spring security配置文件配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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 pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--
配置具体的规则
auto-config="true" 不用自己编写登录的页面,框架提供默认登录页面
use-expressions="false" 是否使用SPEL表达式(没学习过)
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" -->
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
<!-- 定义跳转的具体的页面
login-page:登录界面
default-target-url:登录成功目标url
authentication-failure-url:登录失败
authentication-success-forward-url:登录成功跳转页面
-->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
<!-- 关闭跨域请求 -->
<security:csrf disabled="true"/>
<!-- 退出 -->
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/login.jsp"/>
</security:http>
<!-- 切换成数据库中的用户名和密码 -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService"> <!-- 通过这里配置的service找到对应的认证方法 -->
<!-- 配置加密的方式 -->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密类 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!-- 提供了入门的方式,在内存中存入用户名和密码
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
-->
</beans>
3-自定义接口继承UserDetailsService接口
public interface IUserService extends UserDetailsService{}
4-实现类实现重写loadUserByUsername方法
@Service("userService")
@Transactional
public class UserServiceImpl implements IUserService{
@Autowired
private IUserDao userDao;
//使用该service方法完成认证
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userDao.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
//处理自己的用户对象封装成UserDetails对象,
//User user = new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),getAuthority(userInfo.getRoles()));
User user = new User(userInfo.getUsername(),userInfo.getPassword(),userInfo.getStatus()==0?false:true,true,true,true,getAuthority(userInfo.getRoles()));
return user;
}
//作用就是发挥一个list集合,集合装入的是角色描述
public List<SimpleGrantedAuthority> getAuthority(List<Role> roles){
List<SimpleGrantedAuthority> list = new ArrayList<>();
for (Role role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return list;
}
}
5-dao层的处理
public interface IUserDao {
@Select("select * from users where username=#{username}")
public UserInfo findByUsername(String username) throws Exception;//UserInfo 为自己定义的user实体类
}
服务器端方法级别权限控制
服务器端我们可以通过Spring security提供的注解对方法来进行权限控制。
三种类型的注解
Spring Security在方法的权限控制上支持三种类型的注解:
JSR-250注解
@Secured注解
支持表达式的注解
这三种注解默认都是没有启用的,需要单独通过global-method-security元素的对应属性进行启用
开启注解
1-通过配置文件开启
<security:global-method-security jsr250-annotations= "enabled"/>
<security:global-method-security secured-annotations= "enabled"/>
<security:global-method-security pre-post annotations= "disabled"/>
2-通过注解开启
@EnableGlobalMethodSecurity
: Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean。
使用步骤
1-JSR-250注解
第一步:在security的xml配置文件中开启
<security:global-method-securityjsr256-annotationse="enabled"></security:global-method-security>
第二步:在指定方法上使用
// 查询所有产品
@RequestMapping(value = "/findAll.do")
@RolesAllowed("ADMIN")//该方法只有拥有ADMIN角色的用户可以访问
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List<Product> ps = productService.findAll();
mv.addObject("productList",ps);
mv.setViewName("product-list1");
return mv;
}
@RolesAllowed
表示访问对应方法时所应该具有的角色
示例:@RolesAllowed({“USER”,“ADMIN”})
该方法只要具有 “USER”, “ADMIN”任意一种权限就可以访问。这里可以省略前缀ROLE_ ,实际的权限可能是ROLE_ ADMIN
@PermitAIl
表示允许所有的角色进行访问, 也就是说不进行权限控制
@DenyAll
是和PermitAll相反的, 表示无论什么角色都不能访问
第三步:必须在pom.xml文件中导入注解的依赖(即jar包)
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
2-@Secured注解
(由于是security提供,所以不用额外导入jar包)
第一步:在security的xml配置文件中开启注解
<security:global-method-security secured-annotations="enabled"></security:global-method-security>
第二步:在指定方法上使用
//查询所以订单--分页
@RequestMapping("/findAll.do")
@Secured("ROLE_ADMIN")//必须加上前缀ROLE_
public ModelAndView findAll(
@RequestParam(name = "page",required = true,defaultValue = "1")int page,
@RequestParam(name = "pageSize",required = true,defaultValue = "4")int size) throws Exception {
ModelAndView mv = new ModelAndView();
List<Orders> ordersList = ordersService.findAll(page,size);
//pageinfo就是 一个分页bean
PageInfo pageInfo = new PageInfo(ordersList);
mv.addObject("pageInfo",pageInfo);
mv.setViewName("orders-page-list");
return mv;
}
@Secured
注解标注的方法进行权限控制的支持,其值默认为disabled,
示例:
@Secured( "IS_AUTHENTICATED_ ANONYMOUSLY")
public Account readAccount(Long id);
@Secured( "ROLE_TELLER")
注意:在使用JSR25主解时。可以省略ROLE而我们现在使用的secured是不能省略前缀
3-支持表达式的注解
第一步:在security的xml配置文件中开启注解
<security:global-method-security pre-post-annotations="enabled" ></security:global-method-security>
第二步:在指定方法上使用(可以使用SPEL表达式)
@RequestMapping("/findAll.do")
@PreAuthorize("hasRole(ROLE_ADMIN)")//只有具有ROLE_ADMIN角色的用户可以进行添加用户操作
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List<UserInfo> userList= userService.findAll();
mv.addObject("userList",userList);
mv.setViewName("user-list");
return mv;
}
//添加用户
@RequestMapping("/save.do")
@PreAuthorize("authentication. principal. username='tom'")//只有名叫tom的用户可以进行添加用户操作
public String save(UserInfo userInfo) throws Exception {
userService.save(userInfo);
return "redirect:findAll.do";
}
@PreAuthorize
在方法调用之前,基于表达式的计算结果来限制对方法的访问
示例:
@PreAuthorize("#userId == authentication. principal. userId or hasAuthority(‘ADMIN')")
void changePassword(@P( "userId") long userId ){ }
这里表示在changePassword方法执行之前,判断方法参数userId的值是否等于principa1中保存的当前用户的
userId,或者当前用户是否具有ROLE_ ADMIN权限,两种符合其一, 就可以访问该方法。
@PostAuthorize
允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
示例:
@PostAuthorize
User getUser(" return0bject.userId == authentication. principal .userId or
hasPermission( returnObject,' ADMIN')");
@PostFilter
允许方法调用,但必须按照表达式来过滤方法的结果
@PreFilter
允许方法调用,但必须在进入方法之前过滤输入值
页面端权限控制
导入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>version</version>
</dependency>
jsp页面导入标签
<%etaglib prefix="security" uri="http://www.springframework.org/security/tags" %>
常用标签
**
authentication
**可以获取当前正在操作的用户信息
<%-- 当前正在操作的用户 --%>
<security:authentication property="principal.username"></security:authentication>
效果预览
**
authorize
**用于控制页面上某些标签是否可以显示
<security:authorize access="hasRole('ADMIN')">
<a href="${pageContext.request.contextPath}/user/findAll.do">
<i class="fa fa-circle-o"></i> 用户管理
</a>
</security:authorize>
如果登录用户没有admin角色的话无法查看到该项。
使用该标签在security的xml配置文件中需要把 <security:http auto-config=“true” use-expressions=“true”>中的use-expressions设置为true(即使用el表达式)
效果预览
页面使用表达式
- 1-可以通过security的xml配置文件中<security:http auto-config=“true” use-expressions=“true”>中的use-expressions设置为true(即使用el表达式)*
- 2-或者在security的xml配置文件中多配置一个bean
<bean id="webexpressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />