博主名取自《小羊肖恩》中的小羊肖恩,名字为:肖恩,音译为Sean,自己取的姓:阿奇,为符合我们的阅读习惯,连起来组成为ArchieSean。博主志在将博客打造成为个人线上的技术栈,方便自己也方便他人。如博客中有任何错误,请各位指出,谢谢大家。
Spring Security的使用
使用数据库认证
-
在Spring Security中如果想要使用数据进行认证操作,有很多种操作方式,这里我们介绍使
用UserDetails、 UserDetailsService来完成操作。 -
UserDetails
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
**UserDetails是一个接口,我们可以认为UserDetails作用是于封装当前进行认证的用户信息,但由于其是一个 接口,所以我们可以对其进行实现,也可以使用Spring Security提供的一个UserDetails的实现类User来完成 操作 **
- 以下是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; }
登录案例
- 导入坐标
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
</dependency>
- web-xml的配置
<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>
- 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"/>
<!-- 定义跳转的具体的页面 -->
<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">
<!-- 配置加密的方式 -->
<!-- <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>
- service层
// 接口
package com.slxy.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface IUserService extends UserDetailsService {
}
package com.slxy.service.impl;
import com.slxy.dao.IUserDao;
import com.slxy.domain.Role;
import com.slxy.domain.UserInfo;
import com.slxy.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service("userService")
@Transactional
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userDao.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
List<SimpleGrantedAuthority> list = getauthorities(userInfo.getRoles());
User user = new User(userInfo.getUsername(), "{noop}" + userInfo.getPassword(), userInfo.getStatus() == 1 ? true : false,
true, true, true, list);
return user;
}
public List<SimpleGrantedAuthority> getauthorities(List<Role> roles) {
List<SimpleGrantedAuthority> list = new ArrayList<SimpleGrantedAuthority>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
}
return list;
}
}
密码加密实现
- 在spring-security配置文件中配置加密类
<!-- 切换成数据库中的用户名和密码 -->
<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"/>
- 使用
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* @return java.lang.String
* @Description //用户注册
* @Param [userInfo]
**/
@RequestMapping("/save.do")
public String save(UserInfo userInfo) throws Exception {
userInfo.setPassword(bCryptPasswordEncoder.encode(userInfo.getPassword())); //加密操作
userservice.save(userInfo);
return "redirect:findAll.do";
}
服务器端方法级权限控制
- 在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制。Spring Security在方法的权限控制上 支持三种类型的注解,JSR-250注解、@Secured注解和支持表达式的注解,这三种注解默认都是没有启用的,需要 单独通过global-method-security元素的对应属性进行启用
开启注解的使用
- 配置文件
<security:global-method-security jsr250-annotations="enabled"/>
<security:global-method-security secured-annotations="enabled"/>
<security:global-method-security pre-post-annotations="disabled"/>
- 注解开启
@EnableGlobalMethodSecurity :Spring Security默认是禁用注解的,要想开启注解,需要在继承 WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,并在该类中将 AuthenticationManager定义为Bean。
JSR-250注解
- @RolesAllowed表示访问对应方法时所具有的角色。
// 导入坐标
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
- 在spring-security.xml中开启jsr250的注解支持。
示例:
@RolesAllowed({"USER", "ADMIN"}) 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。
* 这里可以省 略前缀ROLE_,实际的权限可能是ROLE_ADMIN
- @PermitAll表示允许所有的角色进行访问,也就是不进行权限控制。
- @DenyAll是和PermitAll相反的,表示无论什么角色都不能访问。
@Secured注解
- @Secured注解的默认状态为disabled
示例:
@Secured("ROLE_TELLER")
public Account readAccount(Long id);
和jsr250的区别:@Secured是spring-security本身提供的方法级权限控制;jsr250是需要额外导入坐标来使用的。@Secured注解在使用时,必须加上“ROLE_”前缀来使用,而jar250注解可以省略前缀。
支持表达式的注解
- @PreAuthorize在方法调用之前,基于表达式的计算结果来限制对方法的访问。
<!--开启表达式的权限控制-->
<security:global-method-security pre-post-annotations="enabled"/>
示例:
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){ }
这里表示在changePassword方法执行之前,判断方法参数userId的值是否等于principal中保存的当前用户的 userId,或者当前用户是否具有ROLE_ADMIN权限,两种符合其一,就可以访问该方法。
- @PostAuthorize允许方法调用,但是如果表达式的计算结果是false,则会抛出一个安全性异常。
示例:
@PostAuthorize User getUser("returnObject.userId == authentication.principal.userId or hasPermission(returnObject, 'ADMIN')");
- @PostFilter允许方法调用,但必须按照表达式来过滤方法的结果。
- @PreFilter允许方法调用,但必须在进入方法之前过滤输入值。
页面端权限控制
- 在jsp页面中使用spring-security的权限控制。
- 导入坐标
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
</dependency>
- 页面导入
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
常用标签
- authentication :代表当前认证对象,也可以获取信息。
<security:authentication property="" htmlEscape="" scope="" var=""/>
1. property: 只允许指定Authentication所拥有的属性,可以进行属性的级联获取,如“principle.username”, 不允许直接通过方法进行调用.
2. htmlEscape:表示是否需要将html进行转义。默认为true
3. scope:与var属性一起使用,用于指定存放获取的结果的属性名的作用范围,默认pageContext。Jsp中拥 有的作用范围都进行进行指定
4. var: 用于指定一个属性名,这样当获取到了authentication的相关信息后会将其以var指定的属性名进行存 放,默认是存放在pageConext中
- authorize: 用于判断权限,根据权限来显示相应的内容。
<security:authorize access="" method="" url="" var=""></security:authorize>
1. access: 需要使用表达式来判断权限,当表达式的返回结果为true时表示拥有对应的权限
2. method:method属性是配合url属性一起使用的,表示用户应当具有指定url指定method访问的权限, method的默认值为GET,可选值为http请求的7种方法
3. url:url表示如果用户拥有访问指定url的权限即表示可以显示authorize标签包含的内容
4. var:用于指定将权限鉴定的结果存放在pageContext的哪个属性中
- accesscontrollist:用于鉴定ACL权限的。其一共定义了三个属性:hasPermission、domainObject和var, 其中前两个是必须指定的。
<security:accesscontrollist hasPermission="" domainObject="" var=""></security:accesscontrollist>
1. hasPermission:hasPermission属性用于指定以逗号分隔的权限列表
2. domainObject:domainObject用于指定对应的域对象
3. var:var则是用以将鉴定的结果以指定的属性名存入pageContext中,以供同一页面的其它地方使用