Spring security 初体验
本帖最后由 萋小磊 于 2019-1-23 21:14 编辑Spring Security 初体验导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.runjava.security</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.1.19</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
认证过程
package cn.runjava.security.springsecurity.system.security;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* @author 郑查磊
*/
@Slf4j
@Component
public class UserDetailsServiceRunJava implements UserDetailsService {
private final String ROLE = "ROLE_";
/**
* 密码加密
*/
@Autowired
private
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询出用户角色信息
String password = username;
// 加密后的密码
String encodePassword = passwordEncoder.encode(password);
// 账户是否启用 激活的概念 如果用户已启用,则设置为 true
boolean enable = true;
// 账户是否过期 如果帐户尚未过期,则设置为 true
boolean accountNonExpired = true;
// 密码是否过期 如果凭据未过期,则设置为 true
boolean credentialsNonExpired = true;
// 账户为被锁定 如果帐户未锁定,则设置为 true
boolean accountNonLocked = true;
// 权限集合 如果他们提供了正确的用户名和密码并且启用了用户,则应授予调用者权限。不是空的。
ArrayList<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
// 默认添加一个角色 为admin
grantedAuthorityList.add(new SimpleGrantedAuthority(ROLE + "ADMIN"));
// 添加一个权限为 queryUser
grantedAuthorityList.add(new SimpleGrantedAuthority("queryUser"));
// 简单版本 默认为true避免这些 概念性的设置
// new User(username, encodePassword, grantedAuthorityList);
User user = new User(username, encodePassword, enable, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorityList);
log.info("登录认证:" + JSONUtil.toJsonPrettyStr(user));
return user;
}
}
Spring security config
package cn.runjava.security.springsecurity.system.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author 郑查磊
*/
@Order(99)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class SecurityWebApplicationInitializerRunJava extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 退出认证后do....
*/
@Autowired
private LogoutSuccessHandlerRunJava logoutSuccessHandler;
/**
* 认证成功后的处理
*/
@Autowired
private AuthenticationSuccessHandlerRunJava authenticationSuccessHandler;
/**
* 失败后的处理
*/
@Autowired
private AuthenticationFailureHandlerRunJava authenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 禁用 csrf-- 这里如果开启了 iframe 会出错 详细情况不清楚
.csrf().disable()
// 在这里的authorizeRequests 权限访问路径
.authorizeRequests()
// localhost:8080/admin
// 例如 resources 下所有的请求 还有signupabout 的请求 都可以任意访问
.antMatchers("/resources/**", "/signup ", "/about", "/system/logout", "/system/login.html").permitAll()
// admin 下所有请求 需要admin 角色才能访问
.antMatchers("/admin/**").hasRole("ADMIN")
// db 请求 需要同时拥有 ADMIN 和 DB的角色
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
// 剩下所有的 请求 都必须经过身份认证 也就是 login
.anyRequest().authenticated()
.and()
// 基于表单认证
.formLogin()
// 认证地址
.loginProcessingUrl("/login")
//认证页面 需要配置一下
.loginPage("/system/login.html")
// 认证时候的 用户名参数
.usernameParameter("username")
// 认证时候的 密码参数
.passwordParameter("password")
// 认证成功后的处理
.successHandler(authenticationSuccessHandler)
// 失败后的处理
.failureHandler(authenticationFailureHandler)
// 认证失败转跳的路径
// .failureForwardUrl("/logerror.html")
// 认证成功后转跳的页面
// .successForwardUrl("/index.html")
.and()
// 设置退出认证URL
// 触发注销的URL(默认值为/logout)如果启用了CSRF保护(默认),则请求也必须是POST。
.logout().logoutUrl("/logout")
// 退出认证成功 跳到认证页面 /login?logout
// .logoutSuccessUrl("/login.html")
// 这里设置退出认证后的一些操作
.logoutSuccessHandler(logoutSuccessHandler)
// 退出时候注销sessiontrue
.invalidateHttpSession(true)
// 退出时删除的cookie信息
.deleteCookies("user");
}
}
退出登录Handler
package cn.runjava.security.springsecurity.system.security;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 郑查磊
* @TODO 退出登录后要做的事情 do....
*/
@Slf4j
@Component
public class LogoutSuccessHandlerRunJava implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("退出登录!" + JSONUtil.toJsonPrettyStr(authentication));
response.sendRedirect("/system/logout.html");
}
}
登录成功Handler
package cn.runjava.security.springsecurity.system.security;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 郑查磊
* @TODO 登录成功后做的事情
*/
@Component
public class AuthenticationSuccessHandlerRunJava implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
authentication.getDetails();
if(authentication instanceof UsernamePasswordAuthenticationToken){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
Object principal = usernamePasswordAuthenticationToken.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
// 设置到cookie 中 该用户的 登录账号
response.addCookie(new Cookie("user",user.getUsername()));
response.sendRedirect("/system/index.html");
}
}
}
}
package cn.runjava.security.springsecurity.system.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 郑查磊
* @TODO 失败后的处理
*/
@Slf4j
@Component
public class AuthenticationFailureHandlerRunJava implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
}
}
编写自定义登录页面的controller
package cn.runjava.security.springsecurity.system.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* @author 郑查磊
*/
public class BaseController {
/**
* 拿到当前登陆的用户信息
*
* @param request
* @return
*/
public String getUserName(HttpServletRequest request) {
HttpSession session = request.getSession();
SecurityContextImpl securityContext = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
return ((UserDetails) securityContext.getAuthentication().getPrincipal()).getUsername();
}
}
package cn.runjava.security.springsecurity.system.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* @author 郑查磊
*/
@Controller
@RequestMapping("/system")
public class SecurityController extends BaseController {
/**
* 自定义登录页面
*
* @return
*/
@GetMapping("/login.html")
public String loginView() {
return "/login";
}
/**
* 首页
*
* @param request
* @param model
* @return
*/
@GetMapping("/index.html")
public String indexView(HttpServletRequest request, Model model) {
model.addAttribute("username", getUserName(request));
return "/index";
}
/**
* 退出前提示
*
* @param request
* @param model
* @return
*/
@GetMapping("/logout.html")
public String logout(HttpServletRequest request, Model model) {
model.addAttribute("username", getUserName(request));
return "/logout";
}
/**
* 登录失败
*
* @param model
* @return
*/
@GetMapping("/logerror")
public String logerror(Model model) {
return "/logerror";
}
}
测试controller
package cn.runjava.security.springsecurity.controller;
import cn.runjava.security.springsecurity.entity.User;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* @author 郑查磊
*/
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getUser")
@PreAuthorize("hasAuthority('user')")
public User getUser(Integer id) {
return new User(id, "小石头", "不告诉你!");
}
@GetMapping("/addUser")
@PreAuthorize("hasRole('ADMIN')")
public User addUser(Integer id) {
return new User(id, "小石头", "不告诉你!");
}
@GetMapping("/queryUser")
@PreAuthorize("hasAuthority('queryUser')")
public User queryUser(Integer id) {
return new User(id, "小石头", "不告诉你!");
}
}
用到的entity
package cn.runjava.security.springsecurity.entity;
import lombok.Data;
/**
* @author 郑查磊
*/
@Data
public class User {
private Integer id;
private String username;
private String password;
public User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
}
自定义的登录页面 首页等...
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Spring Security</title>
</head>
<body>
<h1 th:text="'登录成功! 尊敬的用户: ' + ${username}"></h1>
<a th:href="@{/system/logout.html}">退出登录</a>
</body>
</html>
logerror.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录失败</title>
</head>
<body>
<h1 th:text=""></h1>
<a th:href="@{/login}"></a>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>自定义登录页面</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" th:attr="action=@{/login}">
<h2 class="form-signin-heading">Please sign in</h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required
autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body>
</html>
logout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>退出登录成功!</title>
</head>
<body>
<h1 th:text="'当前要退出的用户: ' + ${username}"></h1>
<a th:href="@{/logout}">点击退出登录</a>
</body>
</html>
总结cn.runjava.security.springsecurity.system.security.UserDetailsServiceRunJava看懂此文章估计需要 基础 估计就是springmvc 和 spring 的一些 bean 概念 都不难...
这里写的多了一点 包括了 自定义登录页面和地址 都可以配置 详细看代码的注释即可主要是这个类 在用户登录时候的认证信息 添加角色 还有密码 没有写读取数据库
可以自己添加上去这里有一个概念的问题就是 ROLE 和 Authority 在spring security 中是没有这种概念的但是我们做设计的时候可以做一些变通
这里下次写 还有403错误 可以添加拦截 哈哈 下次补上其实配置不难 就是资源的权限配置以及
这个用户的认证过程2点其他的就是你自己对于接口的权限配置了注意的坑: .logoutSuccessUrl("/login.html") 和 .logoutSuccessHandler(logoutSuccessHandler) 只能存在一个 建议在Handler中 能做的更完善 可控项目
下载地址: 链接: https://pan.baidu.com/s/1_8xmY0mki2C0f8vmSXPW0w 提取码: 77jq
摘抄博客:https://www.runjava.cn/archives/spring-boot-security
2015带你飞 发表于 2019-1-23 21:53
shiro了解一下,你这个东西太重了,虽然颗粒度很小但是shiro足够你用了
有时间去看看shiro但是能用security我就不会用shiro
后期转换oauth2简单 2019-03-11 14:31:36.718 ERROR 24244 --- w.a.UsernamePasswordAuthenticationFilter : An internal error occurred while trying to authenticate the user.
org.springframework.security.authentication.InternalAuthenticationServiceException: null
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:123) ~
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144) ~
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200) ~
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94) ~
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException: null
at com.proaim.system.security.UserDetailsServiceRunJava.loadUserByUsername(UserDetailsServiceRunJava.java:40) ~
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:108) ~
... 54 common frames omitted
2019-03-11 14:31:36.718INFO 24244 --- .s.s.AuthenticationFailureHandlerRunJava : 登录失败:null
头皮发麻,基础都忘光了 a7780425 发表于 2019-1-23 21:08
头皮发麻,基础都忘光了
其实大部分一些config 不难理解 其次就是auth的使用 这个相对于其他框架还算是容易懂的吧
使用起来也简单 问下....如何去保证账户只有三处登录状态 ql_zth 发表于 2019-1-23 21:21
问下....如何去保证账户只有三处登录状态
此处的登录状态是session级别的
不太懂你说的那种情况
shiro了解一下,你这个东西太重了,虽然颗粒度很小但是shiro足够你用了 先收藏下, 一直用security 刚好,我也学一下,留着评分,回来反馈和问问题~
页:
[1]
2