依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
config
继承 WebSecurityConfigurerAdapter
@Component
public class LoginSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 重写configure(HttpSecurity http)
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义的登录页(通过controller跳转到login.html)
.loginPage("/toLogin")
//登录请求路径
.loginProcessingUrl("/login")
//成功跳转页面
.successForwardUrl("/")
//失败跳转
.failureForwardUrl("/toError");
http.authorizeRequests()
//放行资源
.antMatchers("/toLogin").permitAll()
//所有资源需要认证才能登录
.anyRequest().authenticated();
http.csrf().disable();
}
//密码加密 官方推荐使用BCryptPasswordEncoder
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
登录逻辑
实现 UserDetailsService 接口
重写 loadUserByUsername(String username) 方法
@Service
@Slf4j
public class LoginDetailsServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("LoginDetailsServiceImpl.loadUserByUsername");
//登录逻辑 一般是从数据库拿数据,
if (!"admin".equals(username)){
//在数据查不到此用户,抛出异常
throw new UsernameNotFoundException("查无此人");
}
//将密码加密 一般从数据库拿 ,数据库存的加密后的密码,这里加密后对比一下,一样==登录成功
String password = passwordEncoder.encode("admin");
log.info("用户={},密码={}",username,password);
//登录成功返回security的User对象 ,并传入用户名,密码和权限
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user"));
}
}
自定义登录成功和失败处理器
分别需要实现 AuthenticationSuccessHandler 和 AuthenticationFailureHandler 接口
public class MySuccessForwardUrl implements AuthenticationSuccessHandler {
private String url;
MySuccessForwardUrl(String url){
this.url=url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//登录成功则重定向到百度
httpServletResponse.sendRedirect(url);
}
}
public class MyFailureForwardUrl implements AuthenticationFailureHandler {
/**
* 要跳转的url
*/
private String url;
MyFailureForwardUrl(String url){
this.url=url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//失败转发到错误页面
httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义的登录页(通过controller跳转到login.html)
.loginPage("/toLogin")
//登录请求路径
.loginProcessingUrl("/login")
// .successForwardUrl("/")
.successHandler(new MySuccessForwardUrl("https://www.baidu.com"))
// .failureForwardUrl("/toError");
.failureHandler(new MyFailureForwardUrl("/toError"));
http.authorizeRequests()
//放行资源
.antMatchers("/toLogin").permitAll()
//所有资源需要认证才能访问
.anyRequest().authenticated();
http.csrf().disable();
}
放行静态资源
http.authorizeRequests()
//放行资源
.antMatchers("/toLogin").permitAll()
//放行静态资源 简单写写
.antMatchers("/js/**","/css/**").permitAll()
//所有资源需要认证才能登录
.anyRequest().authenticated();
权限和角色
权限配置:.antMatchers(“/admin”).hasAnyAuthority(“admin”)
角色配置: .antMatchers(“/admin”).hasRole(“abc”)
直接在后面加
//权限配置
.antMatchers("/admin").hasAnyAuthority("admin")
//角色配置
.antMatchers("/admin").hasRole("abc")
角色必须加上 ROLE_ 前缀
@Service
@Slf4j
public class LoginDetailsServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("LoginDetailsServiceImpl.loadUserByUsername");
//登录逻辑 一般是从数据库拿数据,
if (!"admin".equals(username)){
//在数据查不到此用户,抛出异常
throw new UsernameNotFoundException("查无此人");
}
//将密码加密 一般从数据库拿 ,数据库存的加密后的密码,这里加密后对比一下,一样久等了成功
String password = passwordEncoder.encode("admin");
log.info("用户={},密码={}",username,password);
//登录成功返回security的User对象 ,并传入用户名,密码和权限
// 角色必须加上 ROLE_ 前缀
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,ROLE_abc"));
}
}
ip限定
//只有此ip可以访问
.antMatchers("/admin").hasIpAddress("127.0.0.1")
自定义403
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"权限不住,请联系管理员\"}");
writer.flush();
writer.close();
}
}
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
注解开发
使用 @EnableGlobalMethodSecurity 开启security注解模式
开启后如果程序没有权限等会报500
org.springframework.security.access.AccessDeniedException
| 注解 | 作用 |
|---|---|
| @EnableGlobalMethodSecurity | 开启security注解模式 |
| @Secured() | 角色判断,参数要以 ROLE_ 开头 |
| @PreAuthorize() | 方法或类在执行之前先判断权限 |
| @PostAuthorize() | 方法或类在执行结束后判断权限 |
@EnableGlobalMethodSecurity
@EnableGlobalMethodSecurity 默认全是false,所以要使用要手动开启
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class BootSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(BootSecurityApplication.class, args);
}
}
@Secured
@Secured("ROLE_abc")
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin";
}
}
@PreAuthorize
@PreAuthorize 可以验证权限也可以验证角色
@PreAuthorize允许以 ROLE_开头
// @PreAuthorize("admin")
@PreAuthorize("hasRole('admin')")
// @PreAuthorize("hasRole('ROLE_admin')")
@RequestMapping("/admin")
@ResponseBody
public String admin(){
return "admin";
}
}
@PostAuthorize作用一样,只是执行顺序不一样,但是这个注解不常用。
RememberMe功能实现
RememberMe --> 记住我功能
用户只需要在登录时添加 remember-me复选框,取值为true,stringsecurity会自动把用户信息存储到数据源中,以后就可以不等录进行访问。
存入数据库 导入mybatis-plus和数据库链接依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
记住我配置
//记住我
http.rememberMe()
//失效时间秒
.tokenValiditySeconds(60)
//自定义登录逻辑
.userDetailsService(detailsService)
//持久层对象
.tokenRepository(persistentTokenRepository);
}
配置持久层
//在yaml中配置数据源,注入数据源
@Resource
private DataSource dataSource;
//将配置的getPersistentTokenRepository注入
@Resource
private PersistentTokenRepository persistentTokenRepository;
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
//PersistentTokenRepository的实现类JdbcTokenRepositoryImpl
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//设置数据源
jdbcTokenRepository.setDataSource(dataSource);
//第一次启动的时候建表,后面要关闭,不然表存在了再去创建会报错
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
html
<form th:action="@{/login}" method="post">
<input type="text" name="username" placeholder="用户名"/><br/>
<input type="text" name="password" placeholder="密码"/><br/>
<!-- name必须为remember-me value为true -->
记住我:<input type="checkbox" name="remember-me" value="true"/><br/>
<input type="submit" value="登录"/>
</form>
退出登录
http.logout().logoutUrl("/toLogin");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
hello
<a href="/logout">退出</a>
</body>
</html>
csrf
csrf 就是跨站请求伪造,通过伪造用户请求访问受信任站点的非法请求
springsecuruty默认开启 csrf,默认会拦截请求,为了保证不是其他第三方网站访问,要求访问是携带参数_csrf值为token(服务端产生)的内容,如果token和服务端匹配成功,则正常访问。
删除前面的 http.csrf().disable();
<form th:action="@{/login}" method="post">
<!-- th:value="${_csrf.token}"从服务端拿到这个值 name必须为 _csrf th:if 如果有值就放进去,没有就为null -->
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="_csrf">
<input type="text" name="username" placeholder="用户名"/><br/>
<input type="text" name="password" placeholder="密码"/><br/>
<!-- name必须为remember-me value为true -->
记住我:<input type="checkbox" name="remember-me" value="true"/><br/>
<input type="submit" value="登录"/>
</form>
Oauth2
~