官网操作及阅读 https://spring.io/
点击 projects 选择 spring security
首先看到的overview下有简介和特色或者功能还有快速开始。
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。
Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求。
特征:
对身份验证和授权的全面且可扩展的支持
防止会话固定、点击劫持、跨站点请求伪造等攻击
Servlet API 集成
与 Spring Web MVC 的可选集成
入门分为:
入门(Servlet)
入门 (WebFlux)
入门(servlet) 可以点击下载最小的 Spring Boot + Spring Security 应用程序。
这个项目其实就是创建spring boot 添加 下面的依赖
1 2 3 4 5 6 7 8 9 10 <properties > <spring-boot.version > 2.3.7.RELEASE</spring-boot.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 @RestController public class IndexController { @GetMapping("/") public String index () { return "success....." ; } }
这时启动spring boot 项目
可以直接输入http://localhost:8080/login 会跳转到springsecurity提供的登录页。也可以直接访问http://localhost:8080/也会自动跳转。
根据官网提示,它会创建一个用户名为user的用户,密码在启动日志打印里。
登录成功后会自动跳转到http://localhost:8080/显示sucess
读取用户名和密码的方式(表单、基本、摘要) 表单 (默认) 默认配置相当于下面的配置
1 2 3 4 5 6 7 8 9 10 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin(Customizer.withDefaults()) .authorizeRequests().anyRequest().authenticated(); } }
测试表单同上。
基本 httpBasic是由http协议定义的最基础的认证方式。每次请求时,在请求头Authorization参数中附带用户/密码的base64编码,参考base64。这个方式并不安全,不适合在web项目中使用。但它是一些现代主流认证的基础,而且在spring security的oauth中,内部认证默认就是用的httpBasic。
配置
1 2 3 4 5 6 7 8 9 10 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.httpBasic(Customizer.withDefaults()) .authorizeRequests().anyRequest().authenticated(); } }
这时使用浏览器访问,不再会跳转到登录页,而会弹出浏览器的用户名密码窗口。
在调试窗口中查看该请求的响应头,其中有个WWW-Authenticate: Basic realm=”Realm”。
WWW-Authenticate:服务器告知浏览器代理认证工作。
Basic:认证类型为Basic。
realm=”Realm”:认证域名为Realm
realm realm=”Realm”:指认证域名为Realm。在未认证用户请求不同的接口时,后台根据给该接口分配的域,可以响应不同的realm名,并且用不同用户名/密码进行认证。所以用户每请求一个新Realm的url,都会弹框要求用新Realm的用户名/密码进行认证,就好比不同的角色登录只能请求属于该角色的url。httpBasic默认realm名为Realm,可以用以下方式配置。
1 http.httpBasic().realmName("Realm" )
此时的响应码为401,根据401和以上响应头,浏览器会接管工作,它会弹出上面那个框要求输入用户名/密码,并将其拼接成“用户名:密码”格式,中间是一个冒号,再用base64编码成xxx,然后在请求头中附加Authorization:Basic xxx,发给后台认证。后台需要用base64解码xxx,再认证用户名/密码。
认证错误:浏览器会保持弹框。
认证成功:浏览器会缓存有效的base64编码,在之后的请求中,浏览器都会在请求头中添加有效编码。 可以使用浏览器的清除 cookies及其他网站数据清除该用户名密码。
摘要认证 您不应该在现代应用程序中使用摘要式身份验证,因为它不被认为是安全的。最明显的问题是您必须以明文、加密或 MD5 格式存储密码。所有这些存储格式都被认为是不安全的。相反,您应该使用 Digest Authentication 不支持的单向自适应密码散列(即 bCrypt、PBKDF2、SCrypt 等)存储凭据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder () { return NoOpPasswordEncoder.getInstance(); } @Override public void configure (HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated().and() .addFilter(digestAuthenticationFilter()) .exceptionHandling().accessDeniedPage("/403" ) .authenticationEntryPoint(digestAuthenticationEntryPoint()) .and() .csrf().disable(); } @Bean public DigestAuthenticationFilter digestAuthenticationFilter () { DigestAuthenticationFilter filter= new DigestAuthenticationFilter (); filter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint()); filter.setUserDetailsService(userDetailsService()); return filter; } @Bean public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint () { DigestAuthenticationEntryPoint point = new DigestAuthenticationEntryPoint (); point.setRealmName("realm" ); point.setKey("key" ); return point; } @Bean public UserDetailsService userDetailsService () { return new UserDetailsService () { public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { List<GrantedAuthority> authorities = new ArrayList <GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority ("auth" )); return new User (username, "123" , true , true , true , true , authorities); } }; } }
测试同http基本认证
自定义登录页面(认证还是默认的) formLogin自动配置了一些url和页面:
/login (get):登录页面,任意没有登录的请求都会跳转到这里,就是上面看到的那个页面。 /login (post):登录接口,在登录页面点击登录,会请求这个接口。 /login?error:用户名或密码错误,跳转到该页面。 /:登录成功后,默认跳转的页面,/会重定向到index.html,这个页面要你自己实现。 /logout:注销页面。 /login?logout:注销成功跳转页面。
添加依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > <version > 2.6.7</version > </dependency >
添加templates/login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html > <html xmlns ="http://www.w3.org/1999/xhtml" xmlns:th ="https://www.thymeleaf.org" > <head > <title > Please Log In</title > </head > <body > <h1 > Please Log In</h1 > <div th:if ="${param.error}" > Invalid username and password.</div > <div th:if ="${param.logout}" > You have been logged out.</div > <form th:action ="@{/login}" method ="post" > <div > <input type ="text" name ="username" placeholder ="Username" /> </div > <div > <input type ="password" name ="password" placeholder ="Password" /> </div > <input type ="submit" value ="Log in" /> </form > </body > </html >
添加跳转的controller
1 2 3 4 5 6 7 @Controller class LoginController { @GetMapping("/login") String login () { return "login" ; } }
添加配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin().loginPage("/login" ) .and().authorizeRequests() .antMatchers("/login" ).permitAll() .anyRequest().authenticated(); } }
密码存储 密码编码器(PasswordEncoder) 不管密码存储到哪都需要密码编码器,因为明文密码是不安全的。
spring security提供了很多实现类
这些实现类都以算法名开头 XXXPasswordEncoder
1 2 3 4 5 6 public static void main (String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder (); String result = encoder.encode("123" ); System.out.println(result); encoder.matches("123" , result); }
spring security 提供了四种方式
把密码存储到内存中 这里使用的是默认页面,内存里存储用户名和密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean public UserDetailsService users () { UserDetails user = User.builder() .username("user" ) .password("$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy" ) .roles("USER" ) .build(); UserDetails admin = User.builder() .username("admin" ) .password("$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy" ) .roles("USER" , "ADMIN" ) .build(); return new InMemoryUserDetailsManager (user, admin); } }
测试访问 http://localhost:8080/ 自动跳转到登录页,输入用户名user 密码 123,发现跳转“/页面。
兼容多种密码解码器
1 2 3 4 public void configure (AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user" ).password("{bcrypt}$2a$10$89rbg/..2V1hoRzXtlDa9ejjIzsqeO.kQgmkQnAa//xDzJLoyJgAu" ).authorities("auth" ).and() .withUser("old" ).password("{noop}123" ).authorities("old" );
此时不需要再设置passwordEncoder,而是在密码中加上前缀进行区分。如以前的用户old,给其密码加上前缀{noop},表示未加密。新用户密码前缀为{bcrypt},表示bcrypt加密。系统为根据前缀自动识别你的加密方式。在自认定认证中,同样可以根据前缀判断加密方式。
把密码存储到jdbc关系数据库中 为了简单演示,使用嵌入式数据库H2
添加依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > <version > 2.1.212</version > </dependency >
复制ddl文件到你的resources目录中,这个文件是创建表的sql
目录在spring security的core包的org/springframework/security/core/userdetails/jdbc/users.ddl
1 2 3 create table users(username varchar_ignorecase(50 ) not null primary key,password varchar_ignorecase(500 ) not null ,enabled boolean not null );create table authorities (username varchar_ignorecase(50 ) not null ,authority varchar_ignorecase(50 ) not null ,constraint fk_authorities_users foreign key(username) references users(username));create unique index ix_auth_username on authorities (username,authority);
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(H2) .addScript("users.ddl") .build(); } @Bean public UserDetailsService users(DataSource dataSource) { UserDetails user = User.builder() .username("user") .password("$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy") .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password("$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy") .roles("USER", "ADMIN") .build(); JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource); users.createUser(user); users.createUser(admin); return users; } }
测试同理。
LDAP存储 使用嵌入式的ldap
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > com.unboundid</groupId > <artifactId > unboundid-ldapsdk</artifactId > <version > 4.0.14</version > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-ldap</artifactId > <type > jar</type > <scope > compile</scope > </dependency >
添加配置文件users.ldif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 dn : ou=groups,dc=springframework,dc=org objectclass : top objectclass : organizationalUnit ou : groups dn : ou=people,dc=springframework,dc=org objectclass : top objectclass : organizationalUnit ou : people dn : uid=admin,ou=people,dc=springframework,dc=org objectclass : top objectclass : person objectclass : organizationalPerson objectclass : inetOrgPerson cn : Rod Johnson sn : Johnson uid : admin userPassword : $2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy dn : uid=user,ou=people,dc=springframework,dc=org objectclass : top objectclass : person objectclass : organizationalPerson objectclass : inetOrgPerson cn : Dianne Emu sn : Emu uid : user userPassword : $2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy dn : cn=user,ou=groups,dc=springframework,dc=org objectclass : top objectclass : groupOfNames cn : user dn : cn=admin,ou=groups,dc=springframework,dc=org objectclass : top objectclass : groupOfNames cn : admin
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean UnboundIdContainer ldapContainer () { return new UnboundIdContainer ("dc=springframework,dc=org" , "classpath:users.ldif" ); } @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication() .userDnPatterns("uid={0},ou=people" ) .groupSearchBase("ou=groups" ) .contextSource() .url("ldap://localhost:53389/dc=springframework,dc=org" ) .and() .passwordCompare() .passwordEncoder(passwordEncoder()) .passwordAttribute("userPassword" ) .and() .ldapAuthoritiesPopulator(new LdapAuthoritiesPopulator () { @Override public Collection<? extends GrantedAuthority > getGrantedAuthorities(DirContextOperations userData, String username) { return Collections.emptyList(); } }); } }
测试同上。
自定义数据存储(一般使用这个) 添加UserDetailsService实现类
1 2 3 4 5 6 7 8 9 10 11 12 public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { if (!username.equals("admin" )) { throw new UsernameNotFoundException ("用户不存在" ); } return new User (username, "$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy" , AuthorityUtils.commaSeparatedStringToAuthorityList("admin1, admin2" )); } }
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
并发会话控制(只在一个账号登录只存在一台设备上) (1)踢掉前面的登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin(Customizer.withDefaults()) .authorizeRequests().anyRequest().authenticated(); http.sessionManagement().maximumSessions(1 ); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
(2)禁止新的登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin(Customizer.withDefaults()) .authorizeRequests().anyRequest().authenticated(); http.sessionManagement().maximumSessions(1 ).maxSessionsPreventsLogin(true ); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
记住我 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin(Customizer.withDefaults()) .authorizeRequests().anyRequest().authenticated(); http.rememberMe().userDetailsService(customUserDetailsService).tokenValiditySeconds(3600 ); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
登录时选择remember me ,登录后查看cookies会有一个remember me 的cookies,这时关闭浏览器不用重新登录。
登出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin().permitAll(); http.logout().logoutUrl("/logout" ).logoutSuccessHandler(new LogoutSuccessHandler () { @Override public void onLogoutSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("退出成功" ); } }); http.authorizeRequests().anyRequest().authenticated(); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
还是不能自定义退出接口
无权限403错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin().permitAll(); http.logout().logoutUrl("/logout" ).logoutSuccessHandler(new LogoutSuccessHandler () { @Override public void onLogoutSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("退出成功" ); } }); http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler () { @Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("处理无权限错误返回JSON" ); PrintWriter printWriter = response.getWriter(); printWriter.println("处理无权限错误返回JSON" ); printWriter.flush(); printWriter.close(); } }); http.authorizeRequests().antMatchers("/login/success" ).hasAnyRole("admin" ); http.authorizeRequests().anyRequest().authenticated(); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
授权 http.authorizeRequests() 就是授权。
hasRole(String role)
返回true
当前主体是否具有指定角色。例如,hasRole('admin')
默认情况下,如果提供的角色不以“ROLE_”开头,它将被添加。这可以通过修改defaultRolePrefix
on来定制DefaultWebSecurityExpressionHandler
。
hasAnyRole(String… roles)
返回true
当前主体是否具有任何提供的角色(以逗号分隔的字符串列表形式给出)。例如,hasAnyRole('admin', 'user')
默认情况下,如果提供的角色不以“ROLE_”开头,它将被添加。这可以通过修改defaultRolePrefix
on来定制DefaultWebSecurityExpressionHandler
。
hasAuthority(String authority)
true
如果当前委托人具有指定的权限,则返回。例如,hasAuthority('read')
hasAnyAuthority(String… authorities)
返回true
当前主体是否具有任何提供的权限(以逗号分隔的字符串列表形式给出)例如,hasAnyAuthority('read', 'write')
principal
允许直接访问代表当前用户的主体对象
authentication
允许直接访问Authentication
从SecurityContext
permitAll
总是评估为true
denyAll
总是评估为false
isAnonymous()
返回true
当前主体是否为匿名用户
isRememberMe()
true
如果当前主体是记住我的用户,则返回
isAuthenticated()
true
如果用户不是匿名的,则返回
isFullyAuthenticated()
true
如果用户不是匿名用户或记住我的用户,则返回
hasPermission(Object target, Object permission)
返回true
用户是否有权访问给定权限的目标。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission)
返回true
用户是否有权访问给定权限的目标。例如,hasPermission(1, 'com.example.domain.Message', 'read')
有四个注释支持表达式属性,以允许调用前和调用后的授权检查,还支持过滤提交的集合参数或返回值。它们是@PreAuthorize、@PreFilter和@PostAuthorize、@PostFilter。
自定义权限验证bean 自定义验证bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class WebSecurity { public boolean check (String permission) { Set<String> permissions = new HashSet <>(); permissions.add("userManage:user:add" ); if (permissions.contains(permission)) { return true ; } return false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController public class IndexController { @PreAuthorize("@webSecurity.check('userManage:user:add')") @GetMapping("/") public String index () { return "success....." ; } @PreAuthorize("@webSecurity.check('userManage:user:aa')") @GetMapping("/index1") public String index1 () { return "index1....." ; } @GetMapping("/error") public String error () { return "error....." ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin().permitAll(); http.logout().logoutUrl("/logout" ).logoutSuccessHandler(new LogoutSuccessHandler () { @Override public void onLogoutSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("退出成功" ); } }); http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler () { @Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { System.out.println("处理无权限错误返回JSON" ); PrintWriter printWriter = response.getWriter(); printWriter.println("处理无权限错误返回JSON" ); printWriter.flush(); printWriter.close(); } }); http.authorizeRequests().anyRequest().authenticated(); } @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean CustomUserDetailsService customUserDetailsService () { return new CustomUserDetailsService (); } }
使用默认的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { if (!username.equals("admin" )) { throw new UsernameNotFoundException ("用户不存在" ); } Set<GrantedAuthority> authoritySet = new HashSet <>(); SimpleGrantedAuthority role = new SimpleGrantedAuthority ("ROLE_admin1" ); authoritySet.add(role); return new User (username, "$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy" , authoritySet); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RestController public class LoginController { @PreAuthorize("hasRole('admin1')") @GetMapping("/login/{status}") public String login (@PathVariable String status) { System.out.println(status); if ("auth" .equals(status)) { return "没有登录" ; } if ("fail" .equals(status)) { return "登录失败" ; } if ("success" .equals(status)) { return "登录成功" ; } if ("logout" .equals(status)) { return "注销成功" ; } return "" ; } }
整合JWT 1 2 3 4 5 <dependency > <groupId > com.auth0</groupId > <artifactId > java-jwt</artifactId > <version > 3.19.2</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 @Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { if (!username.equals("admin" )) { throw new UsernameNotFoundException ("用户不存在" ); } return new User (username, "$2a$10$euvsO7nBJjnyRgtgLG7EfurLQD2cql6lHPMewpDxp7v8p2i0NZDVy" , AuthorityUtils.commaSeparatedStringToAuthorityList("admin1, admin2" )); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @RestController public class LoginController { @Autowired private AuthenticationManager authenticationManager; @GetMapping(value = "/login") public String Login (@RequestParam("username") String username, @RequestParam("password") String password) { Authentication authentication = null ; try { authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken (username, password)); } catch (Exception e) { if (e instanceof BadCredentialsException) { throw new RuntimeException (e.getMessage()); } else { throw new RuntimeException (e.getMessage()); } } Map<String, String> claimMap = new HashMap <>(); claimMap.put("username" , "abc" ); return TokenUtli.GenerateToken(claimMap); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController public class IndexController { @GetMapping("/") public String index () { return "success....." ; } @GetMapping("/error") public String error () { return "error....." ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 public class TokenUtli { public static final String ISSUER = "auth0" ; public static final String AUDIENCE = "Client" ; public static final String KEY = "123456" ; public static final Algorithm ALGORITHM = Algorithm.HMAC256(TokenUtli.KEY); public static final Map<String, Object> HEADER_MAP = new HashMap <String, Object>() { { put("alg" , "HS256" ); put("typ" , "JWT" ); } }; public static String GenerateToken (Map<String, String> claimMap) { Date nowDate = new Date (); Date expireDate = TokenUtli.AddDate(nowDate, 2 * 60 ); JWTCreator.Builder tokenBuilder = JWT.create(); for (Map.Entry<String, String> entry : claimMap.entrySet()) { tokenBuilder.withClaim(entry.getKey(), entry.getValue()); } String token = tokenBuilder.withHeader(TokenUtli.HEADER_MAP) .withIssuer(TokenUtli.ISSUER) .withAudience(TokenUtli.AUDIENCE) .withIssuedAt(nowDate) .withExpiresAt(expireDate) .sign(TokenUtli.ALGORITHM); return token; } private static Date AddDate (Date date, Integer minute) { if (null == date) { date = new Date (); } Calendar calendar = new GregorianCalendar (); calendar.setTime(date); calendar.add(Calendar.MINUTE, minute); return calendar.getTime(); } public static boolean VerifyJWTToken (String webToken) throws Exception { if (StringUtils.isEmpty(webToken)) { return false ; } String[] token = webToken.split(" " ); if (token.length <= 1 ) { throw new Exception ("token错误" ); } if (token[1 ].equals("" )) { throw new Exception ("token错误" ); } JWTVerifier verifier = JWT.require(TokenUtli.ALGORITHM).withIssuer(TokenUtli.ISSUER).build(); DecodedJWT jwt = verifier.verify(token[1 ]); List<String> audienceList = jwt.getAudience(); String audience = audienceList.get(0 ); Map<String, Claim> claimMap = jwt.getClaims(); for (Map.Entry<String, Claim> entry : claimMap.entrySet()) { } Date issueTime = jwt.getIssuedAt(); Date expiresTime = jwt.getExpiresAt(); return true ; } public static Map<String, Claim> parseToken (String webToken) throws Exception { if (StringUtils.isEmpty(webToken)) { return null ; } String[] token = webToken.split(" " ); if (token.length <= 1 ) { throw new Exception ("token错误" ); } if (token[1 ].equals("" )) { throw new Exception ("token错误" ); } JWTVerifier verifier = JWT.require(TokenUtli.ALGORITHM).withIssuer(TokenUtli.ISSUER).build(); DecodedJWT jwt = verifier.verify(token[1 ]); return jwt.getClaims(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = getToken(request); try { Map<String, Claim> map = TokenUtli.parseToken(token); if (map != null && map.get("username" ) != null ) { String username = map.get("username" ).asString(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken (username, null , null ); authenticationToken.setDetails(new WebAuthenticationDetailsSource ().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } catch (Exception e) { e.printStackTrace(); System.out.println("报错了" ); } } private String getToken (HttpServletRequest request) { String token = request.getHeader("Authorization" ); return token; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 EnableGlobalMethodSecurity(prePostEnabled = true , securedEnabled = true )public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; @Autowired private PasswordEncoder passwordEncoder; @Autowired private CustomUserDetailsService customUserDetailsService; @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean @Override public AuthenticationManager authenticationManagerBean () throws Exception { return super .authenticationManagerBean(); } @Override protected void configure (HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests().antMatchers("/login" ).permitAll(); http.authorizeRequests().anyRequest().authenticated(); http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder); } }
rouyi配置详解 还是看rouyi项目吧