Java学习笔记-Day88 Spring Security安全框架
一、Spring Security的简介
Spring Security正是Spring 家族中的成员,Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
应用的安全性包括 用户认证(Authentication) 和 用户授权(Authorization) 两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security的主要核心功能为认证和授权,整个架构也是基于这两个核心功能去实现的。
二、Spring Security和Shiro比较
Spring Security 的特点:
(1)和Spring无缝整合。
(2)全面的权限控制。
(3)专门为Web开发而设计。
(4)旧版本不能脱离Web环境使用。
(5)新版本对整个框架进行了分层抽取,分成了核心模块和Web 模块。单独引入核心模块就可以脱离Web环境。
(6)重量级。
Shiro 的特点:
(1)轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
(2)通用性。
(3)好处:不局限于Web环境,可以脱离Web环境使用。
(4)缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
Shiro和Security相比,各有千秋,在 SSM 中整合 Spring Security 相对麻烦,所以 Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多,很多场景中,Shiro也够用。Spring Boot 横空出世后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
因此,常见的安全管理技术栈的组合是这样的:
- SSM + Shiro、JSP + Servlet + Shiro
- Spring Boot + Spring Security、Spring Cloud + Spring Security
- Spring Boot + Shiro +Jwt、Spring Cloud + Shiro +Jwt
三、Spring Security的使用
只导入Spring Security 依赖,不进行任何配置的话,登录默认使用的用户名是 user, 密码则是随机生成并使用UUID加密后的密码(会在控制台输出)。这里我们会修改成使用数据库中用户表的用户名和密码进行登录,数据库表中的密码存储的是使用UUID加密后的密码。
步骤:
(1)点击 New Project -> 选择Spring Initializr -> 点击Next -> 输入Group和Artifact,Java Version选择8 ->点击Next。
(2)选择Web的Spring Web、Security的Spring Security、SQL的Mybatis Framework和MySQL Driver。
(3)输入Project name和Project location,点击 Finish 完成。
(4)修改配置文件application.properties。
server.port=8081
logging.level.org.springframework.security=trace
#数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zmalldb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#指定mybatis中配置的映射文件的地址
mybatis.mapper-locations=classpath:/mapper/*.xml
(5)将idea连接MySQL数据库,点击idea右侧的Database -> 点击 + 号 -> 选择Data Source -> 选择MySQL。
在General选项页输入IP地址、端口号、账号、密码、数据库名。点击Test Connecton,会出现时区错误,点击set time zone设置时区。将servertimezone修改为Asia/Shanghai。
添加成功后,会在右侧显示如下界面。
(6)选择tbl_user表右键点击,使用 MyBatisX-Generator 插件生成tbl_user表的entity类和Mapper类。在 LoginUserMapper.java 和 LoginUserMapper.xml 中创建selectByName方法。注意:用户表中一定要有用户名和用户密码这两个字段。
- LoginUser.java
package com.etc.securitydemo.entity;
import java.io.Serializable;
/**
* null
* @TableName tbl_user
*/
public class LoginUser implements Serializable {
/**
* 序号
*/
private Integer userid;
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String userpwd;
/**
*
*/
private String usersex;
/**
* 电话
*/
private String userphone;
/**
* 0表示异常/1表示正常
*/
private Integer userstate;
/**
* 收货地址
*/
private String address;
private static final long serialVersionUID = 1L;
/**
* 序号
*/
public Integer getUserid() {
return userid;
}
/**
* 序号
*/
public void setUserid(Integer userid) {
this.userid = userid;
}
/**
* 账号
*/
public String getUsername() {
return username;
}
/**
* 账号
*/
public void setUsername(String username) {
this.username = username;
}
/**
* 密码
*/
public String getUserpwd() {
return userpwd;
}
/**
* 密码
*/
public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}
/**
*/
public String getUsersex() {
return usersex;
}
/**
*/
public void setUsersex(String usersex) {
this.usersex = usersex;
}
/**
* 电话
*/
public String getUserphone() {
return userphone;
}
/**
* 电话
*/
public void setUserphone(String userphone) {
this.userphone = userphone;
}
/**
* 0表示异常/1表示正常
*/
public Integer getUserstate() {
return userstate;
}
/**
* 0表示异常/1表示正常
*/
public void setUserstate(Integer userstate) {
this.userstate = userstate;
}
/**
* 收货地址
*/
public String getAddress() {
return address;
}
/**
* 收货地址
*/
public void setAddress(String address) {
this.address = address;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
LoginUser other = (LoginUser) that;
return (this.getUserid() == null ? other.getUserid() == null : this.getUserid().equals(other.getUserid()))
&& (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
&& (this.getUserpwd() == null ? other.getUserpwd() == null : this.getUserpwd().equals(other.getUserpwd()))
&& (this.getUsersex() == null ? other.getUsersex() == null : this.getUsersex().equals(other.getUsersex()))
&& (this.getUserphone() == null ? other.getUserphone() == null : this.getUserphone().equals(other.getUserphone()))
&& (this.getUserstate() == null ? other.getUserstate() == null : this.getUserstate().equals(other.getUserstate()))
&& (this.getAddress() == null ? other.getAddress() == null : this.getAddress().equals(other.getAddress()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getUserid() == null) ? 0 : getUserid().hashCode());
result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
result = prime * result + ((getUserpwd() == null) ? 0 : getUserpwd().hashCode());
result = prime * result + ((getUsersex() == null) ? 0 : getUsersex().hashCode());
result = prime * result + ((getUserphone() == null) ? 0 : getUserphone().hashCode());
result = prime * result + ((getUserstate() == null) ? 0 : getUserstate().hashCode());
result = prime * result + ((getAddress() == null) ? 0 : getAddress().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", userid=").append(userid);
sb.append(", username=").append(username);
sb.append(", userpwd=").append(userpwd);
sb.append(", usersex=").append(usersex);
sb.append(", userphone=").append(userphone);
sb.append(", userstate=").append(userstate);
sb.append(", address=").append(address);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
- LoginUserMapper.java
package com.etc.securitydemo.mapper;
import com.etc.securitydemo.entity.LoginUser;
/**
* @Entity com.etc.securitydemo.entity.LoginUser
*/
public interface LoginUserMapper {
int deleteByPrimaryKey(Long id);
int insert(LoginUser record);
int insertSelective(LoginUser record);
LoginUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(LoginUser record);
int updateByPrimaryKey(LoginUser record);
LoginUser selectByName(String username);
}
- LoginUserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.etc.securitydemo.mapper.LoginUserMapper">
<resultMap id="BaseResultMap" type="com.etc.securitydemo.entity.LoginUser">
<id property="userid" column="userid" jdbcType="INTEGER"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="userpwd" column="userpwd" jdbcType="VARCHAR"/>
<result property="usersex" column="usersex" jdbcType="CHAR"/>
<result property="userphone" column="userphone" jdbcType="VARCHAR"/>
<result property="userstate" column="userstate" jdbcType="INTEGER"/>
<result property="address" column="address" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
userid,username,userpwd,
usersex,userphone,userstate,
address
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tbl_user
where userid = #{userid,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tbl_user
where userid = #{userid,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.LoginUser" useGeneratedKeys="true">
insert into tbl_user
( userid,username,userpwd
,usersex,userphone,userstate
,address)
values (#{userid,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},#{userpwd,jdbcType=VARCHAR}
,#{usersex,jdbcType=CHAR},#{userphone,jdbcType=VARCHAR},#{userstate,jdbcType=INTEGER}
,#{address,jdbcType=VARCHAR}))
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.LoginUser" useGeneratedKeys="true">
insert into tbl_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userid != null">userid,</if>
<if test="username != null">username,</if>
<if test="userpwd != null">userpwd,</if>
<if test="usersex != null">usersex,</if>
<if test="userphone != null">userphone,</if>
<if test="userstate != null">userstate,</if>
<if test="address != null">address,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userid != null">userid = #{userid,jdbcType=INTEGER},</if>
<if test="username != null">username = #{username,jdbcType=VARCHAR},</if>
<if test="userpwd != null">userpwd = #{userpwd,jdbcType=VARCHAR},</if>
<if test="usersex != null">usersex = #{usersex,jdbcType=CHAR},</if>
<if test="userphone != null">userphone = #{userphone,jdbcType=VARCHAR},</if>
<if test="userstate != null">userstate = #{userstate,jdbcType=INTEGER},</if>
<if test="address != null">address = #{address,jdbcType=VARCHAR},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.etc.securitydemo.entity.LoginUser">
update tbl_user
<set>
<if test="username != null">
username = #{username,jdbcType=VARCHAR},
</if>
<if test="userpwd != null">
userpwd = #{userpwd,jdbcType=VARCHAR},
</if>
<if test="usersex != null">
usersex = #{usersex,jdbcType=CHAR},
</if>
<if test="userphone != null">
userphone = #{userphone,jdbcType=VARCHAR},
</if>
<if test="userstate != null">
userstate = #{userstate,jdbcType=INTEGER},
</if>
<if test="address != null">
address = #{address,jdbcType=VARCHAR},
</if>
</set>
where userid = #{userid,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.etc.securitydemo.entity.LoginUser">
update tbl_user
set
username = #{username,jdbcType=VARCHAR},
userpwd = #{userpwd,jdbcType=VARCHAR},
usersex = #{usersex,jdbcType=CHAR},
userphone = #{userphone,jdbcType=VARCHAR},
userstate = #{userstate,jdbcType=INTEGER},
address = #{address,jdbcType=VARCHAR}
where userid = #{userid,jdbcType=INTEGER}
</update>
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tbl_user
where username = #{username,jdbcType=VARCHAR}
</select>
</mapper>
(7)在 src\test\java\com\etc\securitydemo 目录下创建测试类来测试Mapper类。输入encode变量是使用UUID加密后的字符串。
package com.etc.securitydemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootTest
class SecuritydemoApplicationTests {
@Test
void contextLoads() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println(encode);
boolean matches = passwordEncoder.matches("123456", encode);
System.out.println(matches);
}
}
(8)创建config包,并在该包下创建SecurityConfig类。(重要)
package com.etc.securitydemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
(9)创建service包,并在该包下创建UserDetailServiceImpl实现类。(重要)
package com.etc.securitydemo.service;
import com.etc.securitydemo.entity.LoginUser;
import com.etc.securitydemo.mapper.LoginUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service("userDetailServiceImpl")
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private LoginUserMapper loginUserMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LoginUser loginUser = loginUserMapper.selectByName(s);
if(loginUser == null){
throw new UsernameNotFoundException("用户不存在!");
}
User user = new User(loginUser.getUsername(),loginUser.getUserpwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
return user;
}
}
(10)创建controller包,并在该包下创建Controller类。
package com.etc.securitydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@GetMapping("hello")
@ResponseBody
public String GetHello(){
return "Hello,Spring Security!";
}
}
(11)在SecuritydemoApplication运行类前添加@MapperScan注解。
package com.etc.securitydemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.etc.securitydemo.mapper")
public class SecuritydemoApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritydemoApplication.class, args);
}
}
(12)运行SecuritydemoApplication启动类。访问网页 127.0.0.1:8081/hello
,输入数据库中用户表的用户称和密码进行登录。注意:数据库用户表中的密码存储的是使用UUID加密后的密码。
四、自定义登录页
(1)在pom.xml加入thymeleaf模板引擎支持。
<!--加入thymeleaf模板引擎支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
(2)在templates目录中加入login.html和index.html文件。
- login.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>后台登录</title>
</head>
<body>
<div>
<div>登录</div>
<form method="post" action="/login">
<input name="username" placeholder="用户名" type="text"><br/>
<input name="password" placeholder="密码" type="password" ><br/>
<input value="登录" type="submit">
</form>
</div>
</body>
</html>
- index.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>后台</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
(3)在application.properties中加入thymeleaf配置。
##thymeleaf配置
spring.thymeleaf.cache=false
spring.thymeleaf.suffix=.html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.encoding=utf-8
spring.thymeleaf.mode=HTML
spring.thymeleaf.content-type=text/html
(4)创建一个LoginController文件。
package com.etc.securitydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/tologin")
public String toLogin() {
return "login";
}
@GetMapping("/toindex")
public String toIndex() {
return "index";
}
}
(5)在SecurityConfig类中加入配置。登录页面为thymeleaf,不能直接访问,通过 / tologin转发,所以loginPage为/ tologin。
@Configuration
//WebSecurityConfigurerAdapter web安全配置适配器
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailServiceImpl")
private UserDetailServiceImpl userDetailServiceImpl;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailServiceImpl);
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception{
//指定表单验证(formlogin)有关的内容
http.formLogin().loginProcessingUrl("/login")//处理登录的控制器的url地址
.defaultSuccessUrl("/toindex",true)//登录成功后默认跳转url
.loginPage("/tologin");//实际上是通过/tologin的地址,转发到/templates/login.html
//哪些资源可以直接访问
http.authorizeRequests()
.antMatchers("/login","/tologin","/back/css/**","/back/js/**","/back/fonts/**").permitAll()
.anyRequest().authenticated();
//暂时关闭csrf校验
http.csrf().disable();
}
}
(6)在浏览器中访问 127.0.0.1:8081/hello
时,会跳转到/tologin去,也就是自定义的login.html页面。输入用户账号和密码,登录成功后,会跳转到index.html。
五、Spring Security的授权
1、url 匹配规则详解
在SecurityConfig配置类中http.authorizeRequests()主要是对url 进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体格式为:
(1)url 匹配规则.权限控制方法
通过上面的格式可以有很多url 匹配规则和很多权限控制方法。这些内容进行各种组合就形成了Spring Security 中的授权。在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面。
(2)anyRequest()
在之前认证过程中我们就已经使用过anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。例如:
anyRequest().authenticated();
(3)antMatchers()
方法定义如下:
public C antMatchers(String... antPatterns)
参数是可变参数,每个参数是一个ant 表达式,用于匹配URL规则。
规则如下:
?
:匹配一个字符
*
:匹配0 个或多个字符
**
:匹配0 个或多个目录
在实际项目中经常需要放行所有静态资源,下面演示放行js文件夹下所有脚本文件。
.antMatchers("/js/**").permitAll()
还有一种配置方式是只要是.js 文件都放行
.antMatchers("/**/*.js").permitAll()
(4)regexMatchers()
使用正则表达式进行匹配。和antMatchers()主要的区别就是参数,antMatchers()参数是ant 表达式,regexMatchers()参数是正则表达式。
演示所有以.js 结尾的文件都被放行。
.regexMatchers(".+[.]js").permitAll()
无论是antMatchers()还是regexMatchers()都具有两个参数的方法,其中第一个参数都是HttpMethod,表示请求方式,当设置了HttpMethod 后表示只有设定的特定的请求方式才执行对应的权限设置。
(5)mvcMatchers()
mvcMatchers()适用于配置了servletPath 的情况。servletPath 就是所有的URL 的统一前缀。在SpringBoot 整合SpringMVC 的项目中可以在application.properties 中添加下面内容设置ServletPath。
spring.mvc.servlet.path= /etc
在Spring Security 的配置类中配置.servletPath()是mvcMatchers()返回值特有的方法,antMatchers()和regexMatchers()没有这个方法。在servletPath()中配置了servletPath 后,mvcMatchers()直接写SpringMVC 中@RequestMapping()中设置的路径即可。
.mvcMatchers("demo").servletPath("/etc").permitAll()
如果不习惯使用mvcMatchers()也可以使用antMatchers(),下面代码和上面代码是等效的。
.antMatchers("/etc/demo").permitAll()
2、权限控制方法详解
Spring Security 匹配了URL 后调用了permitAll()表示无需授权,随意访问。在Spring Security 中提供了多种内置控制。
(1)permitAll():表示所匹配的URL任何人都允许访问。
(2)uthenticated():表示所匹配的URL需要被认证才能访问。
(3)anonymous():表示可以匿名访问匹配的URL。和permitAll()效果类似,对于匿名访问的用户,Spring Security支持为其建立一个匿名AnonymousAuthenticationToken存放在SecurityContextHolder中,这就是匿名认证。这样在以后进行权限认证或者做其它操作时就不需要再判断SecurityContextHolder中持有的Authentication对象是否为null,直接把它当做一个正常的Authentication进行使用。
(4)denyAll():表示所匹配的URL 都不允许被访问。
(5)rememberMe():被“remember me”的用户允许访问。
(6)fullyAuthenticated():用户通过正常登录认证的而不是被remember me 的,才可以访问。
3、角色权限判断
(1)hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User 对象时指定的。下图中admin 就是用户的权限。admin 严格区分大小写。在配置类中通过hasAuthority(“admin”)设置具有admin 权限时才能访问。
.antMatchers("/main.html").hasAuthority("admin")
(2)hasAnyAuthority(String …)
如果用户具备给定权限中某一个,就允许访问。下面代码中由于大小写和用户的权限不相同,所以用户无权访问/main.html
.antMatchers("/main.html").hasAnyAuthority("adMin","admiN")
(3)hasRole(String)
如果用户具备给定角色就允许访问。否则出现403。参数取值来源于自定义登录逻辑UserDetailsService 实现类中创建User 对象时给User 赋予的授权。在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_admin 其中admin 是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写admin 即可。否则启动报错。
(4)hasAnyRole(String …)
如果用户具备给定角色的任意一个,就允许被访问
(5)hasIpAddress(String)
如果请求是指定的IP 就允许被访问。可以通过request.getRemoteAddr()获取ip 地址。需要注意的是在本机进行测试时localhost 和127.0.0.1 输出的ip地址是不一样的。
六、Spring Security的授权使用
1、Spring Security的授权案例
(1)创建Controller类,超级管理员和普通管理员访问不同的url地址:
- 拥有admin的角色的用户可以访问AdminController类
package com.etc.securitydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("admin")
public class AdminController {
@GetMapping("add")
@ResponseBody
public String add(){
return "admin add";
}
@GetMapping("list")
@ResponseBody
public String list(){
return "admin list";
}
}
- 拥有manager的角色的用户可以访问CommonManagerController类
package com.etc.securitydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("manager")
public class CommonManagerController {
@GetMapping("add")
@ResponseBody
public String add(){
return "manager add";
}
@GetMapping("list")
@ResponseBody
public String list(){
return "manager list";
}
}
(2)修改SecurityConfig配置类。
package com.etc.securitydemo.config;
import com.etc.securitydemo.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
//WebSecurityConfigurerAdapter web安全配置适配器
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailServiceImpl")
private UserDetailServiceImpl userDetailServiceImpl;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailServiceImpl);
}
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception{
//指定表单验证(formlogin)有关的内容
http.formLogin().loginProcessingUrl("/login")//处理登录的控制器的url地址
.defaultSuccessUrl("/toindex",true)//登录成功后默认跳转url
.loginPage("/tologin");//实际上是通过/tologin的地址,转发到/templates/login.html
//哪些资源可以直接访问
http.authorizeRequests() .antMatchers("/login","/tologin","/back/css/**","/back/js/**","/back/fonts/**").permitAll()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/manager/**").hasRole("manager")
.anyRequest().authenticated();
//暂时关闭csrf校验
http.csrf().disable();
}
}
(3)通过MybatisX generator插件生成role表的Role类、RoleMapper类、RoleMapper的xml文件。
- Role.java
package com.etc.securitydemo.entity;
import java.io.Serializable;
/**
* null
* @TableName tbl_role
*/
public class Role implements Serializable {
/**
*
*/
private Integer id;
/**
*
*/
private String name;
/**
*
*/
private String remark;
private static final long serialVersionUID = 1L;
/**
*/
public Integer getId() {
return id;
}
/**
*/
public void setId(Integer id) {
this.id = id;
}
/**
*/
public String getName() {
return name;
}
/**
*/
public void setName(String name) {
this.name = name;
}
/**
*/
public String getRemark() {
return remark;
}
/**
*/
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Role other = (Role) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
&& (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", name=").append(name);
sb.append(", remark=").append(remark);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
- RoleMapper.java
package com.etc.securitydemo.mapper;
import com.etc.securitydemo.entity.Role;
import java.util.List;
/**
* @Entity com.etc.securitydemo.entity.Role
*/
public interface RoleMapper {
int deleteByPrimaryKey(Long id);
int insert(Role record);
int insertSelective(Role record);
Role selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Role record);
int updateByPrimaryKey(Role record);
List<Role> selectByUserId(Integer uid);
}
- RoleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.etc.securitydemo.mapper.RoleMapper">
<resultMap id="BaseResultMap" type="com.etc.securitydemo.entity.Role">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="remark" column="remark" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id,name,remark
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from tbl_role
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tbl_role
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.Role"
useGeneratedKeys="true">
insert into tbl_role
( id,name,remark
)
values (#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR}
))
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.securitydemo.entity.Role"
useGeneratedKeys="true">
insert into tbl_role
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="name != null">name,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">id = #{id,jdbcType=INTEGER},</if>
<if test="name != null">name = #{name,jdbcType=VARCHAR},</if>
<if test="remark != null">remark = #{remark,jdbcType=VARCHAR},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.etc.securitydemo.entity.Role">
update tbl_role
<set>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
</if>
<if test="remark != null">
remark = #{remark,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.etc.securitydemo.entity.Role">
update tbl_role
set
name = #{name,jdbcType=VARCHAR},
remark = #{remark,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select tbl_role.id,tbl_role.name,tbl_role.remark from tbl_role inner join tbl_user_role on tbl_role.id = tbl_user_role.rid where tbl_user_role.uid = #{uid,jdbcType=INTEGER}
</select>
</mapper>
(4)修改UserDetailServiceImpl类。
- UserDetailServiceImpl.java
package com.etc.securitydemo.service;
import com.etc.securitydemo.entity.LoginUser;
import com.etc.securitydemo.entity.Role;
import com.etc.securitydemo.mapper.LoginUserMapper;
import com.etc.securitydemo.mapper.RoleMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("userDetailServiceImpl")
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private LoginUserMapper loginUserMapper;
@Autowired
private RoleMapper roleMapper;
Logger logger = LoggerFactory.getLogger(UserDetailServiceImpl.class);
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LoginUser loginUser = loginUserMapper.selectByName(s);
logger.info(loginUser.toString());
if(loginUser == null){
throw new UsernameNotFoundException("用户不存在!");
}
List<Role> roles = roleMapper.selectByUserId(loginUser.getUserid());
List<GrantedAuthority> authorityList = new ArrayList<>();
for(Role role : roles){
authorityList.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
User user = new User(loginUser.getUsername(),loginUser.getUserpwd(),authorityList);
return user;
}
}
2、基于注解的访问控制
&emsp
在Spring Security 中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过在启动类上添加@EnableGlobalMethodSecurity 进行开启后使用,并根据功能的不同,在该注解中添加不同的属性。这些注解可以写到Service 接口或方法上上也可以写到Controller或Controller 的方法上。通常情况下都是写在控制器方法上的,控制接口URL 是否允许被访问。如果没有授权,则报500异常。
@Secured:在方法前或类前,是专门用于判断是否具有角色的,该注解的值为UserDetailServiceImpl类中设置的角色名称,要以ROLE_开头。
package com.etc.securitydemo.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Secured(value="ROLE_admin")
@GetMapping("add")
public String add(){
return "product add";
}
@Secured(value="ROLE_manager")
@GetMapping("list")
public String list(){
return "product list";
}
}