学自:b站的博主:“一岁就会编程”的在线智能办公系统的教学视频
创建一个Spring Boot项目
依赖可以自己导入或创建时添加上
pom文件里添加依賴
<dependencies>
<!--SpringSecurity組件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--web組件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.1.0.2.0</version>
</dependency>
<!--SpringSecurity可以再一些视图技术中进行控制显示效果 在非前后端分离且使用
Springboot的项目中多使用Thymeleaf作为视图展示技术 -->
<!--Thymeleaf对Spring Security的支持都放在thymeleaf-extras-springsecurity中-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
至于建的包,类什么的用图片看吧,写的麻烦
SecurityConfig类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,第一次启动时候需要,第二次启动注释掉
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单提交
http.formLogin()
//
.usernameParameter("username123")
.passwordParameter("password123")
//当发现(/login)时认为是登录,必须和窗体提交的地址一样,去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/showLogin")
//登陆成功后跳转页面必须是post请求
.successForwardUrl("/toMain")
//登陆成功后处理器,不能和successForwardUrl共存
// .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//登录失败后跳转页面,post请求
.failureForwardUrl("/toError");
//登陆失败后处理器,不能和failureForwardUrl共存
//.failureHandler(new MyAuthenticationFailureHandler("/error.html"));
//授权认证
http.authorizeRequests()
//error.html不需要被认证
.antMatchers("/error.html").permitAll()
//login.html不需要被认证
.antMatchers("/showLogin").permitAll()
//.antMatchers("/js/**","/css/","/images/**").permitAll()
//.antMatchers("/**/*.png").permitAll()
//正则表达式匹配
//.regexMatchers(".+[.]png").permitAll()
//.regexMatchers("/demo").permitAll()
//.regexMatchers(HttpMethod.GET,"/demo").permitAll()
//mvc匹配servletPath为特有方法,其他2种匹配方法没有
//看 配置文件.properties,用来规定访问demo,只能通过 开放 (/xxx路径)访问 该路径等效于 .antMatchers("/xxx/demo").permitAll()
//.mvcMatchers("/demo").servletPath("/xxx").permitAll()
//权限判断
//hasAuthority("admin") 加角色权限,只有admin用户才能访问,严格遵守大小写
//.antMatchers("/main1.html").hasAuthority("admin")
//.antMatchers("/main1.html").hasAnyAuthority("admin","admiN")
//对角色进行判断
//.antMatchers("/main1.html").hasRole("abc")
//hasRole('abc')和permitAll()或者其他最终都是通过access()方法来实现的
//.antMatchers("/main1.html").access("hasRole('abc')")
//.antMatchers("/main1.html").hasAnyRole("abC,abc")
//IP地址判断
//本机IP地址用hasIpAdress(),不是本机的话request.getRemoteAddr();
//.antMatchers("/main1.html").hasIpAddress("127.0.0.1")
//access结合自定义方法实现权限控制
//.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");
//所有请求都必须被认证,必须登录之后被访问
.anyRequest().authenticated();
//CSRF跨站请求伪造,也被称为“OneClick Attack”或者“session Riding”通过伪造用户请求访问受信任站点的非法请求访问
//关闭csrf防护
http.csrf().disable();
//异常处理
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
//RememberMe功能,用户只需要在登录时添加remember-me复选框,取值为true,spring security会自动把用户信息存储到数据源中,以后就可以不登录进行访问
//要实现该功能要导入Spring-JDBC,一般来说导入mybatis也一样,
//(记住我)功能
http.rememberMe()
//失效时间,单位秒
.tokenValiditySeconds(60)
//.rememberMeParameter()
//自定义登录逻辑
.userDetailsService(userDetailsService)
//持久层对象
.tokenRepository(persistentTokenRepository);
//退出登录
http.logout()
//默认的
.logoutUrl("logout")
//自定义的
//.logoutUrl("/user/logout")
//退出登录跳转页面
.logoutSuccessUrl("/login.html");
}
}
LoginController类
@Controller
public class LoginController {
/*
* 登录*/
/*@RequestMapping("login")
public String login(){
System.out.println("执行登录方法");
return "redirect:main.html";
}*/
/*跳转成功页面*/
//@Secured()是专门用于判断是否具有角色的,能写在方法或类上,参数要以ROLE_开头,开启批注用@EnableGlobalMethodSecurity(securedEnabled = true)
//@Secured("ROLE_abc")
//@PreAuthorize/@PostAuthorize都是方法或类级别批注,开启批注用@EnableGlobalMethodSecurity(prePostEnabled = true)
//@PreAuthorize表示访问方法或类在执行之前先判断权限,大多数情况下都是使用这个批注,批注的参数和access()方法参数取值相同,都是权限表达式。
//PreAuthorize的表达式允许ROLE_开头,也可以不以ROLE_开头,配置类不允许ROLE_开头
//@PostAuthorize表示方法或类执行结束后判断权限,此批注很少被使用到
//@PreAuthorize("hasRole('abc')")
@PreAuthorize("hasRole('ROLE_abc')")
@RequestMapping("toMain")
public String toMain(){
return "redirect:main.html";
}
/*跳转失败页面*/
@RequestMapping("toError")
public String toError(){
return "redirect:error.html";
}
/*跳转失败页面*/
// @GetMapping("demo")
// @ResponseBody
// public String demo(){
// return "demo";
// }
/*页面跳转*/
@RequestMapping("demo")
public String demo(){
return "demo";
}
/*页面跳转*/
@RequestMapping("showLogin")
public String showLogin(){
return "login";
}
}
MyAccessDeniedHandler类
@Component
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();
}
}
MyAuthenticationFailureHandler类
//自定义登录失败的处理器
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
MyAuthenticationSuccessHandler类
//自定义登录成功的处理器
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
//考虑到安全性,密码输出为null
System.out.println(user.getPassword());
//权限
System.out.println(user.getAuthorities());
httpServletResponse.sendRedirect(url);
}
}
MyService类
//access结合自定义方法实现权限控制
public interface MyService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
MyServiceImpl类
//access结合自定义方法实现权限控制
@Service
public class MyServiceImpl implements MyService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object object = authentication.getPrincipal();
if(object instanceof UserDetails){
UserDetails userDetails = (UserDetails) object;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
UserDetailsServiceImpl类
@Service
/*public class UserDetailsServiceImpl {*/
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了loadUserByUsername");
//查询数据库判断用户名是否存在,如果不存在就会抛出UsernameNotFoundException异常
if(!"admin".equals(username)){
throw new UsernameNotFoundException("不存在");
}
//把查詢出來的密碼(註冊時已經加密)進行解析,或者直接把密碼放入構造方法
String password = pw.encode("123");
// System.out.println(password);
// System.out.println(pw.matches("123",password));
//ROLE_abc:定义角色,ROLE_ 判断条件 abc是角色,在SecurityConfig类里看下一步骤
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,"+
"/main.html,/insert,/delete"));
// (/insert,/delete")权限判断
}
}
satic里的
error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
操作失败,请重新登录<a href="login.html">跳转</a>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用戶名:<input type="text" name="username123" /><br/>
密码:<input type="password" name="password123" /><br/>
记住我:<input type="checkbox" name="remember-me"/><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功
<br/>
<a href="/main1.html">跳转</a>
//自定义的
<!--<a href="/user/logout">退出</a>-->
//默认的
<a href="/logout">退出</a>
</body>
</html>
main1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
main1.html
</body>
</html>
templates里的
demo.html
<!DOCTYPE html>
<!--在HTML中引入thymeleaf命名空间和security命名空间-->
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br/>
登录账号:<span sec:authentication="principal.username"></span><br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authorities"></span><br/>
客戶端地址:<span sec:authentication="detail.remoteAddress"></span><br/>
sessionId:<span sec:authentication="details.sessionId"></span><br/>
<br/>
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">刪除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('abc')">新增</button>
<button sec:authorize="hasRole('abc')">刪除</button>
<button sec:authorize="hasRole('abc')">修改</button>
<button sec:authorize="hasRole('abc')">查看</button>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}" />
用戶名:<input type="text" name="username123" /><br/>
密碼:<input type="password" name="password123" /><br/>
記住我:<input type="checkbox" name="remember-me"/><br/>
<input type="submit" value="登錄" />
</form>
</body>
</html>