Security
需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置
spring.security.user.password=123456
spring.security.user.name=tom
spring.security.user.roles=admin
当用户登陆系统时,会有一个角色.
入门
关于配置类的的自我定义
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置登录页面
http.formLogin();
//权限的配置
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
调用authorizeRequests()的意思是指通过authorizeRequests()方法来开始请求权限配置,anyRequest().fullyAuthenticated()是对http所有的请求必须通过授权认证才可以访问。除了fullyAuthenticated方法,还可以使用authenticated方法,这两个有什么区别呢?
也就是说,fullyAuthenticated不仅可以让有权限的用户访问,还可以让remember-me的用户访问。所以,如果登录页面有记住我,一定要使用fullyAuthenticated。
关于自定义登录页面:
后端配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置登录页面
http.formLogin().loginPage("/login");
//权限的配置
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义登录页</title>
</head>
<body>
<form action="/login" method="post">
用户名: <input type="text" name="username" value="zhangsan" /> <br/>
密码: <input type="password" name="password" value="123456" /> <br/>
<input type="submit">
</form>
</body>
</html>
controller的拦截
@Controller
public class UserLoginController {
@RequestMapping("/login")
public String toFirstPage(){
return "login";
}
}
1. 大体的运行流程是这样的,首先用户访问server端口(例如:localhost:8080),然后security自动跳转到"localhost:8080/login",即登录界面,然后我们把这个路径拦截下来导向我们自定义的登录界面即可,
这是登录界面的配置
2.然后是自定义登录的一些权限配置.
3.这里面有一个错误
解决方法:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置登录页面
http.formLogin().loginPage("/login").permitAll();
//权限的配置
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
原因是上面的权限配置已经说了:通过authorizeRequests()方法来开始请求权限配anyRequest().fullyAuthenticated()是对http所有的请求必须通过授权认证才可以访问。
除fullyAuthenticated方法.所以就出现登录界面的请求也需要验证下能登录,所以,加上
permitAll()让登录界面通过.进行登录
csrf的定义
第一种解决方法:隐藏参数的方式
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义登录页</title>
</head>
<body>
<!--th:action="@{/login}"-->
<form action="/login" method="post">
用户名: <input type="text" name="username" value="zhangsan" /> <br/>
密码: <input type="password" name="password" value="123456" /> <br/>
<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >
<input type="submit">
</form>
</body>
</html>
第二种方式是springboot2.1新的解决办法,其实质也是添加了一个隐藏标签.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义登录页</title>
</head>
<body>
<form th:action="@{/login}" action="/login" method="post">
用户名: <input type="text" name="username" value="zhangsan" /> <br/>
密码: <input type="password" name="password" value="123456" /> <br/>
<!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >-->
<input type="submit">
</form>
</body>
</html>
第三种方式后台直接关闭csrf
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf().disable();
//配置登录页面
http.formLogin().loginPage("/login").permitAll();
//权限的配置
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
退出配置
其实security自带退出功能的
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定义登录页</title>
</head>
<body>
<h1>欢迎来到王者荣耀,敌军还有五秒到达战场!请做好准备.</h1>
<form th:action="@{/logout}" action="/login" method="post">
<input type="submit" value="退出系统">
</form>
</body>
</html>
当点击推出功能后,跳到登录界面,再访问/home界面,就不能访问了,需要用到最初的配置,任何url需要权限验证后登录.
退出后,默认进入的自然是登录页面,但是浏览器路径上面,要显示出刚才是退出系统了。所以应该显示的路径是/login?logout,但是由于这个路径没有授权,会再次跳转到登录页面,显示的也就还是/login,所以我们对登出也要进行授权:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//4.关闭csrf
http.csrf().disable();
//1.配置登录页面
http.formLogin().loginPage("/login").permitAll();
//3.登录成功后去的页面
http.formLogin().successForwardUrl("/home");
//5.退出
http.logout().permitAll();
//2.权限的配置
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
退出结果
关于授权的内容
登录成功处理器:
现在我们登录成功的时候直接跳转到了默认页面。有时候登录操作要求要记录一下日志再跳转,或者登录成功后执行一些其它逻辑再跳转,我们可以增加一个登录成功处理器LoginSuccessHandler,这个类需要实现 AuthenticationSuccessHandler 接口,并实现onAuthenticationSuccess方法
首先自定义一个LoginSucessHandler实现AuthenticationSuccessHandler接口
public class LoginSucesessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
}
}
基于内存的认证
需要的类:web服务器上的权限适配器
WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER")
.and()
.withUser("sang").password("123456").roles("USER");
}
}
security中的用户信息加密用的是BCryptPasswordEncoder这个类然后调用它的encode方法:具体实现如下
@Test
public void testBcryptPasswordEncoder(){
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String s = bCryptPasswordEncoder.encode("123456");
System.out.println(s);
}
输出的结果是:
$2a$10$ElhvfHTfM8tx/3RQKpJLj.v2xzoA5SGLGUcep2q1WjBk8HxMwu5iu
有时候需要获得用户信息然后在数据库中保存,所以这时候需要获得用户的信息,然后把用户信息存到数据库,或去用户信息的方式
HttpSecurity
配置好角色之后对资源要进行管理(即对访问路径的通过角色的控制),这是就要用到HttpSecurity来进行简单的自定义配置
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("USER","ADMIN")
.and()
.withUser("tom").password("123456").roles("USER");
}
/**
* 自定义资源管理
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//HttpSecuruty配置类的主要方法的使用
http.authorizeRequests()
//匹配路径,然后配置这个路径下那个角色能通过
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN','USER')")
.antMatchers("/db/**")
.access("hasAnyRole('ADMIN') and hasAnyRole('DBA')")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
}
}
1、CSRF是什么
CSRF(Cross Site Request Forgery),中文是跨站点请求伪造。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。
spring有做这方面的措施是在filter中,我们使用即可.
登陆表单的详细配置
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("USER","ADMIN")
.and()
.withUser("tom").password("123456").roles("USER");
}
/**
* 自定义资源管理
* 一个and为一个设置的结束
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
/**
* 第一步设置路径访问权限
*/
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN','USER')")
.antMatchers("/db/**")
.access("hasAnyRole('ADMIN') and hasAnyRole('DBA')")
.anyRequest()
.authenticated()
.and()
// 开启表单登陆
.formLogin()
// 默认表单登录访问的地址是login,即登陆请求处理接口,无论是移动端还是登陆界面都是这个口,上面配置了用户即身份角色密码
// 等信息,就是通过这个接口验证的
.loginProcessingUrl("/login")
// 如果用户访问了一个未授权的网页就自动跳转到此页面,一般自定义即可,一般是登陆进去的首页页面
.loginPage("/login_page")
.usernameParameter("name")
.passwordParameter("passwd")
// 登陆成功后的处理逻辑
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException {
Object principal = auth.getPrincipal();
resp.setContentType("application/json;charset-utf-8");
PrintWriter writer = resp.getWriter();
resp.setStatus(200);
Map<String,Object> map = new ConcurrentHashMap<>();
map.put("status",200);
map.put("msg", principal);
ObjectMapper objectMapper = new ObjectMapper();
writer.write(objectMapper.writeValueAsString(map));
writer.flush();
writer.close();
}
}).failureHandler(new AuthenticationFailureHandler(){
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
resp.setStatus(401);
Map<String,Object> map = new Hashtable<>();
map.put("status",401);
if(e instanceof LockedException){
map.put("msg","账户被锁定了");
}else if(e instanceof BadCredentialsException){
map.put("msg","账户名或密码错误,请重新登陆");
}else if(e instanceof DisabledException){
map.put("msg","账户被禁用,登陆失败");
}else if(e instanceof AccountExpiredException){
map.put("msg","登陆已过期请重新登陆");
}else if(e instanceof CredentialsExpiredException){
map.put("msg","密码已过期,请重新登陆!");
}else {
map.put("msg","登陆失败!");
}
ObjectMapper objectMapper = new ObjectMapper();
writer.write(objectMapper.writeValueAsString(map));
writer.flush();
writer.close();
}
})
.permitAll()
.and()
.csrf()
.disable();
}
}
多个security的配置
对于业务比较复杂的,开发者需要配置多个HttpSecurity,实现对WebSecurityCofigureAdapter的多次扩展
@Configuration
public class MultiHttpSecurityConfig {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Autowired
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER")
.and()
.withUser("john").password("123456").roles("USER");
}
@Configuration
//优先级
@Order(1)
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/admin/**").authorizeRequests()
.anyRequest().hasRole("ADMIN");
}
}
@Configuration
public static class OtherSecurityConnfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login/**")
.access("hasAnyRole('ADMIN','USER')")
.antMatchers("/user/**")
.hasRole("USER")
// 这两行的作用是除了上面定义的uri,用户访问其他uri都必须通过验证
.anyRequest()
.authenticated()
.and()
// 一下两行是防止第三方恶意网站的攻击
.csrf()
.disable();
}
}
}
密码加密(对称加密md5)
关于md5的使用:
为什么要对密码加密过再存到数据库,2011.11.21csdn有人把600多万个用户的信息放到了网上,并且是吧原生的没经过加密的原生的文件信息放到了数据库中,导致数据的公开,还有就是有的用户好多个系统公用一个密码,导致大量的个人信息的丢失,所以,了我们一般的做法是将敏感的数据加密后放到数据库,其中常用的是md5,对称加密.
加密方案会用到散列函数:我们常用的是md5消息摘要算法,安全散列算法.
只加密还是不够的,在加点料(salt:盐),如下BCryptPasswordEncoder底层实现:
public String encode(CharSequence rawPassword) {
String salt;
if (this.random != null) {
salt = BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
} else {
salt = BCrypt.gensalt(this.version.getVersion(), this.strength);
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}
//md5的加密方式
String md5Password = DigestUtils.md5Hex(password);
md5加密的实例:
@Override
public TaotaoResult login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
/**
* 验证账户密码,前端已经验证,这部分可以不写
*/
if(StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return TaotaoResult.build(500,"用户名或密码不能为空!");
}
try {
//根据用户名在数据库查找用户
TbUserExample condition = new TbUserExample();
condition.createCriteria().andUsernameEqualTo(username);
List<TbUser> userList = tbUserMapper.selectByExample(condition);
if(userList == null || userList.size() < 1) {
return TaotaoResult.build(500,"用户名或密码错误!");
}
TbUser userInfo = userList.get(0);
//对用户的password密码加密
String md5Password = DigestUtils.md5Hex(password);
//数据库存的是md5对称加密后的数据
if(!userInfo.getPassword().equals(md5Password)) {
return TaotaoResult.build(500,"用户名或密码错误!");
}
// 生成用户Token(也可以使用JWT工具生成token,安全性更高
String token = IDUtils.genImageNameByUUID();
// 生成Redis Key
String redisKey = getTokenRedisKey(token);
// 存储到Redis之前,将用户密码清除。(可以降低安全风险)
userInfo.setPassword(null);
// 将用户信息存储到Redis
redisTemplate.opsForValue().set(redisKey, JsonUtils.objectToJson(userInfo));
// 设置Token过期时间
redisTemplate.expire(redisKey, 30, TimeUnit.MINUTES);
// 将Token写入Cookie
CookieUtils.setCookie(request, response, "TT_TOKEN", token, (int)TimeUnit.MINUTES.toSeconds(30));
return TaotaoResult.ok(token);
} catch (Exception e) {
log.error("登录失败", e);
return TaotaoResult.build(500,"登录失败", ExceptionUtil.getStackTrace(e));
}
}
HttpClient使用注意事项:传的参数注意转换成json,默认的实体里面是String类型,没有自动转换,所以我们需要手东转换一下.接受的结果也是一样的,需要我们反序列化一下.
@Override
public TaotaoResult createService(Order order) {
//调用订单系统服务提交订单
CloseableHttpClient httpClient = HttpClients.createDefault();
String resultStr = null;
/**
* ORDER_BASE_URL=http://localhost:8085
* ORDER_CREATE_URL=/order/create
*/
HttpPost httpPost = new HttpPost(ORDER_BASE_URL + ORDER_CREATE_URL);
String orderJson = JsonUtils.objectToJson(order);
try {
httpPost.setEntity(new StringEntity(orderJson, ContentType.APPLICATION_JSON));
CloseableHttpResponse resp = httpClient.execute(httpPost);
HttpEntity entity = resp.getEntity();
resultStr = EntityUtils.toString(entity);
} catch (Exception e) {
e.printStackTrace();
}
//转换成java对象
TaotaoResult taotaoResult = TaotaoResult.format(resultStr);
return taotaoResult;
}
基于内存的简单小项目
首先创建maven项目
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
然后创建启动类
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
创建WebSecurityConfig用来配置用户基本信息和权限的信息
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 配置用户
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","DB","USER")
.and()
.withUser("tom").password("123456").roles("ADMIN","USER")
.and()
.withUser("john").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* 用户请求权限设置
*/
//拦截请求
http.authorizeRequests()
.antMatchers("/admin/**")
.hasAnyRole("ADMIN")
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN','USER')")
.antMatchers("/db/**")
.access("hasAnyRole('ADMIN')")
.anyRequest()
.fullyAuthenticated()
.and()
//登录界面的设置
.formLogin().loginPage("/login")
//默认登陆成功后去的路径
.defaultSuccessUrl("/home",true)
//登陆成功处理的handler
.successHandler(new SucesessHandler())
//允许通过登陆界面
.permitAll()
.and()
.csrf()
.disable();
}
}
登陆成功后可以打一些日志或者其他处理的hangdler
public class SucesessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
System.out.println("恭喜你登陆成功!!!!!!!!!!!!!!");
httpServletResponse.sendRedirect("/home");
}
}
需要注意的时必须在第二个方法中写,在第一个方法中写无效
web层
@Controller
public class SecurityController {
@RequestMapping("/login")
public String toLogin(){
return "login";
}
@RequestMapping("/home")
public String toBome(){
return "home";
}
@RequestMapping("db")
public String toDb(){
return "db";
}
@RequestMapping("/admin")
public String toAdmin(){
return "admin";
}
@RequestMapping("/user")
public String toUser(){
return "user";
}
}
前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>管理员界面</title>
</head>
<body>
<h1>欢迎来到管理员界面</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据库操作界面</title>
</head>
<body>
<h1>欢迎来到数据库操作界面</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="/db">数据库</a>
<a href="/admin">管理员</a>
<a href="/user">普通用户</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录界面</title>
</head>
<body>
<form method="post">
用户名: <input type="text" name="username" value="zhangsan" /> <br/>
密码: <input type="password" name="password" value="123456" /> <br/>
<!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >-->
<input type="submit">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>普通用户</title>
</head>
<body>
<h1>欢迎来到普通用户界面</h1>
</body>
</html>
基于方法的验证:
Web应用的安全管理,主要包括两个方面的内容,一个是用户身份的认证,即用户登录的设计,二是用户授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理
首先既然是基于方法的,那么我们就得知道他是用在service层的,即服务层,有时用在controller也行的只要是加在方法上就没得问题,具体测试做法如下
后端配置如下
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER","DB")
.and()
.withUser("tom").password("123456").roles("ADMIN","USER")
.and()
.withUser("john").password("123456").roles("USER")
.and()
.withUser("jackson").password("123456").roles("DB");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/home",true)
.permitAll()
.and()
.csrf()
.disable();
}
}
然后需要在web层的具体代码和做法:验证不同用户对一个方法的调用
@Controller
public class UserController {
@Autowired
private StudentService studentService;
@RequestMapping("/login")
public String toLogin(){
return "login";
}
@RequestMapping("home")
public String toHome(){
return "home";
}
@RequestMapping("/admin")
public String toAdmin(){
List<Student> students = studentService.findAllStudent();
return "admin";
}
@RequestMapping("/db")
public String toDb(){
List<Student> students = studentService.findAllStudent();
return "db";
}
@RequestMapping("/user")
public String toUser(){
List<Student> students = studentService.findAllStudent();
return "user";
}
}
service层
@Service
public class StudentServiceImpl implements StudentService {
@Override
@Secured("ROLE_ADMIN")
public List<Student> findAllStudent() {
CopyOnWriteArrayList<Student> students = new CopyOnWriteArrayList<>();
Collections.addAll(students,new Student("张三","男",45),new Student("王五","男",36));
return students;
}
}
前端页面略,仿照上面的页面
测试结果:三个都能调用
首先admin超级管理员登上去
然后john登录上去
说明我们配置的没有问题
注意:配置类需要一个PasswordEncoder,这点容易忘.
基于JDBC得security(即基于数据库的security)
前面我们定义用户是在配置文件和代码中定义死的默认用户,一般在开发中是不会这样做的,我们的用户都是来自我们的用户表,存储在数据库中。操作数据库的技术有很多,spring security默认支持了一个JDBC的方式,下面用这个方式来从数据库中查询用户
首先创建三张表:
表结构
role表结构
user表结构
user_role表结构
需要注意的是role表存name是要加ROLE_前缀
然后就是设计开口框架了
pojo
@Setter
@Getter
public class Role {
private Integer id;
private String name;
private String namezh;
}
@Getter
@Setter
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enable;
private Boolean locked;
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(Role role : roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
/**
* 该账户是否未过期
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 该账户是否未锁定
* @return
*/
@Override
public boolean isAccountNonLocked() {
return !locked;
}
/**
* 该账户的密码是否未过期
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 该账户是否可用
* @return
*/
@Override
public boolean isEnabled() {
return enable;
}
}
mapper层
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getUserRolesByUid(Integer id);
}
service层
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if(user == null){
throw new UsernameNotFoundException("账户不存在");
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/home",true)
.permitAll()
.and()
.csrf()
.disable();
}
}
启动类
@SpringBootApplication
@MapperScan(basePackages = "com.security.lession.mapper")
public class SecurityApplication {
/*
@Autowired
private StudentMapper studentMapper;
@PostConstruct
public void students(){
List<Student> students = studentMapper.selectByExample(null);
System.out.println(students);
}*/
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
还有就是角色继承
@Bean
public RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_DBA > ROLE_ADMIN and ROLE_ADMIN > ROLE_USER";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}