spring securoty学习笔记
说明
使用的是spring boot 的方式
设置springSecurity的用户名和密码的三种实现
配置文件方式
1.只需在全局配置文件中配置下列配置
#spring.security.user.name=zhw
#spring.security.user.password=1234556
配置类的方式
package cn.zhw.springsecurity.config;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
//继承WebSecurityConfigurerAdapter类,重写configure方法
public class SecurityConfigZiDingYi extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//加密密码
String password = bCryptPasswordEncoder.encode("123456");
//设置用户名,密码和权限
auth.inMemoryAuthentication().withUser("zhw").password(password).roles("admin");
}
//configura方法需要用到PasswordEncoder类
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
查数据库的方式
1.配置类
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//注入userDetailService类
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//通过userDetailService的方式设置用户名和密码
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.userDetailService的实现类
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
//username是用户输入的权限登录用户名
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",username);
//从数据库中获取数据
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
//判断用户名是否存在在数据库中
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
自定义登录界面和设置不需要认证的路径
1.配置类
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证用户名和密码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置自定义登录页面和不需要认证的路径
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.自定义登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input name="username" type="text"><br/>
密码:<input name="password" type="text"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
3.controller
package cn.zhw.springsecurity.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* 登录controller
*/
@Slf4j
@Controller
@RequestMapping("/demo")
public class LoginController {
@RequestMapping("/hello")
@ResponseBody
public String login(String name){
log.info("进入该方法{}",name);
return "hello";
}
@RequestMapping("/index")
@ResponseBody
public String index(){
log.info("进入该方法");
return "index";
}
}
基于权限访问控制
hasAuthority方法(只适用于匹配一种权限)
1.配置那些路径需要权限
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// hasAuthority方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有roles权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.给登陆的用户指定权限
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
//判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
//给用户添加的权限列表
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
3.当用户没有指定的权限时会出现403错误
hasAnyAuthority方法(适用于有多个权限)
1.配置那些路径需要权限
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.给登陆的用户指定权限
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
// 判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
3.controller
package cn.zhw.springsecurity.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* 登录controller
*/
@Slf4j
@Controller
@RequestMapping("/demo")
public class LoginController {
@RequestMapping("/hello")
@ResponseBody
public String login(String name){
log.info("进入该方法{}",name);
return "hello";
}
@RequestMapping("/index")
@ResponseBody
public String index(){
log.info("进入该方法");
return "index";
}
@RequestMapping("/hasAnyAuthority")
@ResponseBody
public String hasAnyAuthority(){
log.info("进入该方法");
return "hasAnyAuthority";
}
}
4.结果
成功
基于角色访问控制
hasRole方法
1.底层调用的方法
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
} else {
//底层会给添加的角色前面添加ROLE_,所以我们给用户添加权限的时候要角色前面添加ROLE_字符
return "hasRole('ROLE_" + role + "')";
}
}
2.设置需要可以访问的角色
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
//配置基于角色的控制访问
.antMatchers("/demo/hasRole").hasRole("student")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
3.为登录的用户添加角色
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
// 判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles,ROLE_student");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
hasAnyRole方法
1.配置设置需要可以访问的角色
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.antMatchers("/demo/hasRole").hasRole("student")
.antMatchers("/demo/hasAnyRole").hasAnyRole("student","teacher")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.为用户指定角色
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.antMatchers("/demo/hasRole").hasRole("student")
.antMatchers("/demo/hasAnyRole").hasAnyRole("student","teacher")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
自定义403页面
1.创建403页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
p{
color: red;
}
</style>
</head>
<body>
<p>没有访问权限</p>
</body>
</html>
2.配置当没有权限时跳转的页面
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
//配置登陆页面,访问权限和不需要进行验证的请求
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/demo/index").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.antMatchers("/demo/hasRole").hasRole("student")
.antMatchers("/demo/hasAnyRole").hasAnyRole("student","teacher")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
3.测试结果
注解使用
@Secured
1.开启基于注解的全局security配置
package cn.zhw.springsecurity;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@MapperScan({"cn.zhw.springsecurity.mapper"})
@EnableGlobalMethodSecurity(securedEnabled = true) //开启基于注解的security配置
public class SpringsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}
}
2.添加@secured注解
@RequestMapping("/byAnno")
@ResponseBody
//为方法添加访问的角色
@Secured({"ROLE_teacher"})
public String byAnno(){
log.info("进入该方法");
return "byAnno";
}
3.设置用户的角色
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
// 判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles,ROLE_teacher");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
@ PreAuthorize
1.开启配置
package cn.zhw.springsecurity;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@MapperScan({"cn.zhw.springsecurity.mapper"})
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) //开启基于注解的security配置
public class SpringsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}
}
2.controller
@RequestMapping("/byPreAuthorize")
@ResponseBody
/**
* 方法执行之前校验
* 可以用下列的任一方法,进行校验
* hasRole
* hasAnyRole
* hasAuthority
* hasAnyAuthority
*/
@PreAuthorize("hasAnyRole('ROLE_student')")
public String byPreAuthorize(){
log.info("进入该方法");
return "byPreAuthorize";
}
3.设置用户的角色以及权限
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
// 判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles,ROLE_teacher,ROLE_student");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
@PostAuthorize注解
1.开启基于注解的配置
package cn.zhw.springsecurity;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@MapperScan({"cn.zhw.springsecurity.mapper"})
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) //开启基于注解的security配置
public class SpringsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}
}
2.controller方法
@RequestMapping("/byPostAuthorize")
@ResponseBody
//该方法会先执行,然后再进行校验,当没有权限的时候会,进入403页面
@PostAuthorize("hasAnyRole('ROLE_work')")
public String byPostAuthorize(){
log.info("进入该方法");
return "byPostAuthorize";
}
3.配置用户的权限
package cn.zhw.springsecurity.service;
import cn.zhw.springsecurity.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<cn.zhw.springsecurity.domain.User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",s);
cn.zhw.springsecurity.domain.User user = userMapper.selectOne(userQueryWrapper);
// 判断
if (user == null){ //数据库中没有该用户,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("roles,ROLE_teacher,ROLE_student");
return new User(user.getUserName(),new BCryptPasswordEncoder().encode(user.getPassword()),role);
}
}
4.测试结果
执行了方法,但是跳转到了没有访问权限页面
@PostFilter
对返回的数据进行过滤
1.controller
@RequestMapping("/byPreFilter")
@ResponseBody
@PostAuthorize("hasAnyAuthority('admins')")
//该方法会先执行,然后再进行校验,当没有权限的时候会,进入403页面
@PostFilter("filterObject.userName == 'zhw'")
public List<User> byPreFilter(){
log.info("进入该方法");
ArrayList<User> users = new ArrayList<>();
users.add(new User("1","zhw","123456"));
users.add(new User("2","zsh","123456"));
return users;
}
2.结果
只返回了相等的数据
@PreFilter
对传入的数据进行过滤
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {
...
}
用户注销(退出)
1.在配置类中添加配置
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置退出路径和推出后的访问的页面
http.logout().logoutUrl("/logout").logoutSuccessUrl("/demo/hello").permitAll();
//配置没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
//配置登陆页面,访问权限和不需要进行验证的请求
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/success.html").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.antMatchers("/demo/hasRole").hasRole("student")
.antMatchers("/demo/hasAnyRole").hasAnyRole("student","teacher")
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
2.添加退出链接
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!
<a href="/logout">退出</a>
</body>
</html>
基于数据库实现自动登录
实现原理
功能实现
1.创建数据库表
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
)
2.配置类,配置数据源,配置操作数据库对象
//自动登录功能实现
//1.配置数据源
@Autowired
private DataSource dataSource;
//2.配置操作数据库的对象和注入数据源
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setCreateTableOnStartup(true);//自动创建表
jdbcTokenRepository.setDataSource(dataSource);//配置数据源
return jdbcTokenRepository;
}
3.配置类实现自动登录
package cn.zhw.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
// 通过接口的方式自定义spring security的用户名和密码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
//配置认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
//配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置退出路径和推出后的访问的页面
http.logout().logoutUrl("/logout").logoutSuccessUrl("/demo/hello").permitAll();
//配置没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
//配置登陆页面,访问权限和不需要进行验证的请求
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/user/login") //前端登录请求提交的地址
.defaultSuccessUrl("/success.html").permitAll() //登录成功后发送的请求
.and().authorizeRequests()
.antMatchers("/","/demo/hello","/user/login").permitAll() //设置不需要认证的路径
//登陆的用户必须有role权限才可以访问/demo/index
.antMatchers("/demo/index").hasAuthority("roles")
//当用户有其中一个权限既可以访问
.antMatchers("/demo/hasAnyAuthority").hasAnyAuthority("roles","admin")
.antMatchers("/demo/hasRole").hasRole("student")
.antMatchers("/demo/hasAnyRole").hasAnyRole("student","teacher")
.anyRequest().authenticated()
//配置自动登录(记住我)功能
.and().rememberMe().tokenRepository(persistentTokenRepository())
//设置有效时间
.tokenValiditySeconds(60)
//设置查询数据库的service
.userDetailsService(userDetailsService)
.and().csrf().disable(); //关闭csrf防护
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
//自动登录功能实现
//1.配置数据源
@Autowired
private DataSource dataSource;
//2.配置操作数据库的对象和注入数据源
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setCreateTableOnStartup(true);//自动创建表
jdbcTokenRepository.setDataSource(dataSource);//配置数据源
return jdbcTokenRepository;
}
}
4.在登录页面加入复选框
注意:复选框的name值必须为remember-me springsecurity框架才会找到对应的值
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>登陆</title>
</head>
<style>
body{
background-image: url("https://uploadfile.bizhizu.cn/up/00/84/69/0084694a7d0c75849d963eb32fdf2781.jpg");
}
</style>
<body>
<main class="main" role="main">
<div class="container">
<div class="row">
<div class="offset-md-3 col-md-6">
<form class="form-container" method="post" action="/user/login">
<h2>登陆</h2>
<div class="form-group">
<label for="exampleInputEmail1">用户名</label>
<input name="username" type="text" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="exampleInputPassword1">密码</label>
<input name="password" type="password" class="form-control" id="exampleInputPassword1" placeholder="请输入密码">
</div>
<div class="form-group form-check">
<input name="remember-me" type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">记住我</label>
</div>
<button type="submit" class="btn btn-success btn-block">登录</button>
</form>
</div>
</div>
</div>
</main>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>
5.结果
CSRF功能
spring security4.0之后自动开启了csrf防护,并且只对put,delete,post请求(对改变数据库的操作)进行防护
什么是CSRF
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
攻击细节
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
原理图
微服务权限方案
什么是微服务
维基上对其定义为:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务于服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据上下文,选着合适的语言、工具对其进行构建。
微服务认证和授权的过程
UsernamePasswordAuthenticationFilter
用于获取表单提交的数据
PasswordEncoder
该接口用于spring security将密码进行加密
package cn.zhw.springsecurity.security;
import cn.zhw.springsecurity.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
public class DefaultPasswordEncoder implements PasswordEncoder {
//无参构造
public DefaultPasswordEncoder() {
this(-1);
}
//有参构造
public DefaultPasswordEncoder(int i) {
}
//该方法被调用,用于对密码进行MD5的加密
@Override
public String encode(CharSequence charSequence) {
//使用工具类对密码进行加密
String encrypt = MD5.encrypt(charSequence.toString());
//返回密码
return encrypt;
}
/**
*
* @param charSequence 未加密的密码
* @param encodedPassword 加密过的密码
* @return 进行比对看是否二次密码是否相同
*/
@Override
public boolean matches(CharSequence charSequence, String encodedPassword) {
boolean equals = encodedPassword.equals(MD5.encrypt(charSequence.toString()));
return equals;
}
}
logoutHandler退出处理器,当用户退出的时候,使用的处理器
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
//先调用这个方法做认证
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//判断是否为post方法,当为post方法的时候做认证,否则不做认证
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//调用子类的方法获取用户提交的数据
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
//session管理策略
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//调用认证成功的方法
this.successfulAuthentication(request, response, chain, authResult);
}
}
}
UsernamePasswordAuthenticationFilter
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//设置为未认证状态
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
//做身份认证,会调用userDetailServices方法做身份的认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
UsernamePasswordAuthenticationToken
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 530L;
private final Object principal;
private Object credentials;
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
//标记成未认证
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
//标记成已认证
super.setAuthenticated(true);
}